diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..769cb635b --- /dev/null +++ b/.clang-format @@ -0,0 +1,94 @@ +--- +Language: Cpp +AccessModifierOffset: -1 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: true +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: false + BeforeCatch: true + BeforeElse: true + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Allman +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IncludeCategories: + - Regex: '^<.*\.h>' + Priority: 1 + - Regex: '^<.*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseLabels: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: false +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +ReflowComments: true +SortIncludes: false +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: Never +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: true +SpacesInSquareBrackets: false +Standard: Auto +TabWidth: 2 +UseTab: Never +--- +Language: JavaScript +DisableFormat: true \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..4447eb347 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,11 @@ +*.jpg -crlf -diff -merge +*.png -crlf -diff -merge +*.exr -crlf -diff -merge +*.dpx -crlf -diff -merge +*.cin -crlf -diff -merge +*.tiff -crlf -diff -merge +*.tif -crlf -diff -merge +*.eps -crlf -diff -merge +*.nib -crlf -diff -merge +*.nib -crlf -diff -merge +*.pdf -crlf -diff -merge diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..cdf33f69f --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +**/.DS_Store + +.vscode/** +.idea/** + +# Qt generated +**/*.moc +**/moc_*.cpp +**/ui_*.h +**/qrc_*.cpp +**/*.qmlc +**/*.pkg +**/*.dmg + +# Windows +**/*.pdb +**/*.idb + +**/*.xcodeproj/ + +compile_commands.json + +**/__pycache__ +**/*.pyc + +cmake-*/* +build/* +_build/* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..93893400e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "src/lib/files/WFObj"] + path = src/lib/files/WFObj + url = https://github.com/shotgunsoftware/openrv-WFObj.git +[submodule "src/pub"] + path = src/pub + url = https://github.com/shotgunsoftware/openrv-pub.git +[submodule "src/lib/oiio"] + path = src/lib/oiio + url = https://github.com/shotgunsoftware/openrv-oiio.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..b6e3a7bc4 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,11 @@ +repos: + - repo: https://github.com/cheshirekow/cmake-format-precommit + rev: v0.6.13 + hooks: + - id: cmake-format + + - repo: https://github.com/psf/black + rev: 22.8.0 + hooks: + - id: black + language_version: python3.10 diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..5affb6599 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,76 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +CMAKE_MINIMUM_REQUIRED(VERSION 3.24) + +SET(CMAKE_CONFIGURATION_TYPES + "Debug;Release" + CACHE STRING "" FORCE +) + +IF(NOT CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE + "Debug" + ) +ENDIF() + +IF(CMAKE_BUILD_TYPE + AND NOT CMAKE_BUILD_TYPE MATCHES "^(Debug|Release)$" +) + MESSAGE(FATAL_ERROR "Invalid value for CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}") +ENDIF() + +SET(CMAKE_BUILD_TYPE + "${CMAKE_BUILD_TYPE}" + CACHE STRING "CMake Build Type" +) + +MESSAGE(STATUS "Build type: ${CMAKE_BUILD_TYPE}") + +# Specify our own CMake modules so we can include them right after. +SET(CMAKE_MODULE_PATH + ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/defaults ${CMAKE_SOURCE_DIR}/cmake/dependencies ${CMAKE_SOURCE_DIR}/cmake/globals + ${CMAKE_SOURCE_DIR}/cmake/macros +) + +SET(CMAKE_OSX_DEPLOYMENT_TARGET + "10.14" +) +INCLUDE(rv_version) # RV's build system global variables + +PROJECT( + open-rv + VERSION ${RV_VERSION_STRING} + LANGUAGES CXX C +) + +INCLUDE(rv_options) # RV's build options +INCLUDE(rv_targets) # RV's build platform definitions +INCLUDE(rv_globals) # RV's CMake-build global variables +INCLUDE(rv_stage) # RV's local appplication packaging +INCLUDE(CTest) + +# The 'cxx_defaults' module will sets global C/C++ compiler defaults Note that variable such as 'CMAKE_CXX_COMPILER_ID' is only available after the 'project' +# statement just above. +INCLUDE(cxx_defaults) + +# This should handle fetching or checking then compiling required 3rd party dependencies One can simple disable processing of a given dependency by simply +# commenting out the relevant line. (macOS) /usr/local/Cellar/cmake/3.20.3/share/cmake/Modules/FetchContent.cmake +INCLUDE(FetchContent) # once in the project to include the module +INCLUDE(ExternalProject) # once in the project to include the module + +# Force verbosity of the FETCHCONTENT function, thus outputting some progress rather than just stalling CMake. Fetching and expanding an archive takes some +# time, as an indicator that something is actually happening, we'll force verbosity of the FETCHCONTENT function thus printing some minimal progress. +SET(FETCHCONTENT_QUIET + OFF +) + +ADD_SUBDIRECTORY(cmake/dependencies) + +# RV's main code +ADD_SUBDIRECTORY(src) +ADD_SUBDIRECTORY(packages) + +ADD_SUBDIRECTORY(cmake/install) diff --git a/COMMITERS.md b/COMMITERS.md new file mode 100644 index 000000000..3a0665f64 --- /dev/null +++ b/COMMITERS.md @@ -0,0 +1,8 @@ +# Committers + +This is a list of commiters for the Open RV project, sorted alphabetically by first name. + +* Alain Compagnat ([compaga](https://github.com/compaga)) +* Bernard Laberge ([bernie-laberge](https://github.com/bernie-laberge)) +* Kerby Geffrard ([geffrak](https://github.com/geffrak)) +* Roger Nelson ([rogernelson](https://github.com/rogernelson)) \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..bf37977a9 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,33 @@ +# Contributing + +Thank you for your interest in contributing to Open RV. We are looking forward to collaborating with the community to evolve and improve Open RV. + +## Project Roles and Responsibilities + +Please read this document to learn how to contribute to Open RV. + +Start by getting familiar with the [GOVERNANCE](GOVERNANCE.md) document. It details the rules and responsibilities for Contributors, Committers, and Technical Steering Committee members. + +## Committers + +The RV Open Source Project Committers are listed in alphabetical order, by first name, in [COMMITERS](COMMITERS.md) + +## Contributor License Agreement + +Before contributing code to Open RV source code, you must sign a Contributor License Agreement (CLA). + +You will get the opportunity to sign the CLA when you create your first pull request: the Linux Foundation's EasyCLA system will guide you through the process of signing the CLA. + +If you can't to use the EasyCLA system, you can always send a signed CLA to `ori-tsc@aswf.io` (making sure to include your *github username*), and wait for confirmation that we've received it. + +Here are the two possible CLAs: + +There are two contribution agreement types, one for individuals contributing independently, and one for corporations who want to manage a list of contributors for their organization. Please review the documents in the EasyCLA portal to determine which is the right one for you. + +## Coding Conventions + +Please follow the coding conventions and style in each file and in each library when adding new files. + +## Git Workflow + +Follow instructions from the [README](README.md). diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md new file mode 100644 index 000000000..88d3703ec --- /dev/null +++ b/CONTRIBUTORS.md @@ -0,0 +1,13 @@ +# Contributors + +This is a list of the people who have contributed code to the Open RV project, sorted alphabetically by first name. + +* Alain Compagnat ([compaga](https://github.com/compaga)) +* Bernard Laberge ([bernie-laberge](https://github.com/bernie-laberge)) +* Eric Desruisseaux ([eric-desruisseaux-adsk](https://github.com/eric-desruisseaux-adsk)) +* Guillaume Brossard ([guillaume-brossard](https://github.com/guillaume-brossard)) +* Ian Savoie ([savoiei](https://github.com/savoiei)) +* Kerby Geffrard ([geffrak](https://github.com/geffrak)) +* Martin Chesnay ([mchesnay](https://github.com/mchesnay)) +* Nicolas Montmarquette ([nmontmarquette](https://github.com/nmontmarquette)) +* Roger Nelson ([rogernelson](https://github.com/rogernelson)) diff --git a/GOVERNANCE.md b/GOVERNANCE.md new file mode 100644 index 000000000..270ecde83 --- /dev/null +++ b/GOVERNANCE.md @@ -0,0 +1,103 @@ +# Open RV Project Roles and Responsibilities + +Open RV is a project of the Academy Software Foundation and relies on the ASWF governance policies, supported by the Linux Foundation. + +Open RV is overseen by the Open Review Initiative but it has its own governance. + +There are three primary project roles: + +* [Contributors](#contributors) submit code to the project; +* [Committers](#committers) approve code to be included into the project; and +* [Technical Steering Committee](#technical-steering-committee) (TSC) provides overall high-level project guidance. + +## Contributors + +Open RV grows and thrives from assistance from Contributors. Contributors include anyone in the community who contributes code, documentation, or other technical artifacts that have been incorporated into the project's repository. +Anyone can be a Contributor. You need no formal approval from the project, beyond the legal forms. + +### How to Become a Contributor + +* Review the coding standards to ensure your contribution is in line with the project's coding and styling guidelines. +* Sign the Individual CLA, or if you are employed by an organization that might have any claim to IP you create, have your organization sign the Corporate CLA. +* Submit your code as a PR with the appropriate DCO sign-off. + +## Committers + +Project Committers have merge access on the Open RV GitHub repository, or repositories underneath its control, and are responsible for approving submissions by Contributors. + +### Committer Responsibilities + +Typical activities of a Committer include: + +* Helping users and novice contributors. +* Ensuring a response to questions posted to the Open RV-dev developer mail list +* Contributing code and documentation changes that improve the project. +* Reviewing and commenting on issues and pull requests. +* Ensuring that changes and new code meet acceptable standards and are in the long-term interest of the project. +* Participation in working groups. +* Merging pull requests. + +### How to Become a Committer + +Any member of the Open RV community (though typically an existing Committer or TSC member) may nominate an individual making significant and valuable contributions to the Open RV project to become a new Committer. To nominate a new Committer, open an issue in the Open RV repository, send mail to the TSC mail list, or raise the issue at a TSC meeting. +The TSC may periodically review the Committer list to identify inactive Committers. Past Committers are typically given Emeritus status. Emeriti may request that the TSC restore them to active Committer status. + +## Technical Steering Committee + +The Technical Steering Committee (TSC) has final authority over this project. As defined in the project charter, TSC responsibilities include, but are not limited to: + +* Coordinating technical direction of the Project. +* Project governance and contribution policy. +* GitHub repository administration. +* Maintaining the list of additional Committers +* Appointing representatives to work with other open source or open standards communities. +* Discussions, seeking consensus, and where necessary, voting on technical matters relating to the code base that affect multiple projects. +* Coordinating any marketing, events, or communications regarding the project. + +Within the TSC are two elected leadership roles to be held by its members and voted on annually. Any TSC member can express interest in serving in a role, or nominate another member to serve. There are no term limits, and one person may hold multiple roles simultaneously. Should a TSC member resign from a leadership role before their term is complete, a successor shall be elected through the standard nomination and voting process to complete the remainder of the term. + +The leadership roles are: + +* Chair: This position acts as the project manager, organizing meetings and providing oversight to project administration. +* Chief Architect: This position makes all the final calls on design and technical decisions, and is responsible for avoiding "design by committee" pitfalls. + +The chair role is assumed to rotate annually (though there are no term limits, so the TSC may reelect an existing chair). The chief architect position should be a source of stability and coherent design vision, so the TSC is encouraged to choose an architect who can serve for many years and only change architects when it is necessary for the health of the project and its community. + +At the time of election, the TSC will also agree upon which of these two leaders will serve as the Open RV representative for the term. This member represents the project at all ASWF ORI meetings. + +### Current TSC Members + +Current voting members of the TSC are: + +* Chair and Open RV representative: Alain Compagnat ([compaga](https://github.com/compaga)) +* Chief architect: Bernard Laberge ([bernie-laberge](https://github.com/bernie-laberge)) +* Roger Nelson ([rogernelson](https://github.com/rogernelson)) +* [Name] To be filled +* [Name] To be filled + +### TSC Nomination and Succession + +Any proposal for additional members of the TSC may be submitted by Committers, TSC members, or other major stakeholders of the Open RV community by opening an issue outlining their case or raising the issue at a TSC meeting. New TSC members are accepted or rejected by majority vote of the TSC. + +If a TSC member is for an extended period not regularly participating or performing the responsibilities expected of TSC members, the TSC may by majority vote request an alternate TSC member be submitted by that organization, or remove the inactive member from the TSC. +A voting member of the TSC may nominate a successor in the event that such voting member decides to leave the TSC, and the TSC, including the departing member, shall confirm or reject such nomination by a vote. In the event that the departing member's nomination for successor is rejected by vote of the TSC, the departing member shall be entitled to continue nominating successors until one such successor is confirmed by vote of the TSC. If the departing member fails or is unable to nominate a successor, the TSC may nominate one on the departing member's behalf. + +TSC membership is presumed to be retained by the individual even if they change employers. The TSC may take action to ensure that organizational stakeholder representation not become severely disproportionate, for example by urging an organization that loses its sole TSC representative to nominate a new member, or to limit the total number of voting members from any one organization if too many members all move to the same organization. + +### TSC Meetings + +Any meetings of the TSC are intended to be open to the public, except where there is a reasonable need for privacy. The TSC meets regularly in a voice conference call, at a cadence deemed appropriate by the TSC. The TSC Chair moderates the meeting, or appoints another TSC member to moderate in his or her absence. Meetings may also be streamed online where appropriate; connection details will be posted to the Open RV-dev mail list in advance of the scheduled meeting. + +Items are added to the TSC agenda which are considered contentious or are modifications of governance, contribution policy, TSC membership, or release process, in addition to topics involving the high-level technical direction of the project. + +The intention of the agenda is not to approve or review all patches. That should happen continuously on GitHub and be handled by the larger group of Committers. + +Any community member or Contributor can ask that something be reviewed by the TSC at the meeting by logging a GitHub issue. Any Committer, TSC member, or the meeting chair can bring the issue to the TSC's attention by applying the TSC label. + +Prior to each TSC meeting, the meeting chair will share the agenda with members of the TSC. TSC members can also add items to the agenda at the beginning of each meeting. The meeting chair and the TSC cannot veto or remove items. + +The TSC may invite additional persons to participate in a non-voting capacity. + +The meeting chair is responsible for ensuring that minutes are taken and archived in the project repository. + +Due to the challenges of scheduling a global meeting with participants in several time zones, the TSC will seek to resolve as many agenda items as possible outside of meetings on the public mailing list. diff --git a/LICENSE b/LICENSE index 261eeb9e9..8cab519be 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,6 @@ + Open RV code base is licensed pursuant to Apache 2.0, except as otherwise required by the specific licenses of components of the code base. A non-exhaustive list of components which deviate from the requirements of Apache 2.0 are listed in [Notice.txt](Notice.txt) + + Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ @@ -198,4 +201,4 @@ 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. + limitations under the License. \ No newline at end of file diff --git a/Notice.txt b/Notice.txt new file mode 100644 index 000000000..c48bb6034 --- /dev/null +++ b/Notice.txt @@ -0,0 +1,1337 @@ +IMPORTANT: This file contains a non-exhaustive list of required third party components that are not licensed under Apache 2.0. + +OpenEXR + +Copyright (c) 2006-2019 OpenEXR a Series of LF Projects, LLC. 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 name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +Imath + +Copyright Contributors to the OpenEXR Project. 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 name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Libjpeg + +The authors make NO WARRANTY or representation, either express or implied, with respect to this software, its quality, accuracy, merchantability, or fitness for a particular purpose. This software is provided "AS IS", and you, its user, assume the entire risk as to its quality and accuracy. +This software is copyright (C) 1991-1998, Thomas G. Lane. All Rights Reserved except as specified below. +Permission is hereby granted to use, copy, modify, and distribute this software (or portions thereof) for any purpose, without fee, subject to these conditions: +• (1) If any part of the source code for this software is distributed, then this README file must be included, with this copyright and no-warranty notice unaltered; and any additions, deletions, or changes to the original files must be clearly indicated in accompanying documentation. +• (2) If only executable code is distributed, then the accompanying documentation must state that "this software is based in part on the work of the Independent JPEG Group". +• (3) Permission for use of this software is granted only if the user accepts full responsibility for any undesirable consequences; the authors accept NO LIABILITY for damages of any kind. +These conditions apply to any software derived from or based on the IJG code, not just to the unmodified library. If you use our work, you ought to acknowledge us. +Permission is NOT granted for the use of any IJG author's name or company name in advertising or publicity relating to this software or products derived from it. This software may be referred to only as "the Independent JPEG Group's software". +We specifically permit and encourage the use of this software as the basis of commercial products, provided that all warranty or liability claims are assumed by the product vendor. +ansi2knr.c is included in this distribution by permission of L. Peter Deutsch, sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA. ansi2knr.c is NOT covered by the above copyright and conditions, but instead by the usual distribution terms of the Free Software Foundation; principally, that you must include source code if you redistribute it. (See the file ansi2knr.c for full details.) However, since ansi2knr.c is not needed as part of any program generated from the IJG code, this does not limit you more than the foregoing paragraphs do. +The Unix configuration script "configure" was produced with GNU Autoconf. It is copyright by the Free Software Foundation but is freely distributable. The same holds for its supporting scripts (config.guess, config.sub, ltconfig, ltmain.sh). Another support script, install-sh, is copyright by M.I.T. but is also freely distributable. +It appears that the arithmetic coding option of the JPEG spec is covered by patents owned by IBM, AT&T, and Mitsubishi. Hence arithmetic coding cannot legally be used without obtaining one or more licenses. For this reason, support for arithmetic coding has been removed from the free JPEG software. (Since arithmetic coding provides only a marginal gain over the unpatented Huffman mode, it is unlikely that very many implementations will support it.) So far as we are aware, there are no patent restrictions on the remaining code. +The IJG distribution formerly included code to read and write GIF files. To avoid entanglement with the Unisys LZW patent, GIF reading support has been removed altogether, and the GIF writer has been simplified to produce "uncompressed GIFs". This technique does not use the LZW algorithm; the resulting GIF files are larger than usual, but are readable by all standard GIF decoders. +We are required to state that"The Graphics Interchange Format(c) is the Copyright property of CompuServe Incorporated. GIF(sm) is a Service Mark property of CompuServe Incorporated." + +mp4v2 2.0.0 + +The Original Code is MPEG4IP. The Initial Developer of the Original Code is Cisco Systems Inc. Portions created by Cisco Systems Inc are Copyright (C) Cisco Systems Inc. 2001. All Rights Reserved. + +mp4v2 is licensed under the Mozilla Public License v.1.1, which can be found at https://www.mozilla.org/en-US/MPL/1.1/. + + +OpenColorIO v 1.1.1 + +Copyright (c) 2003-2010 Sony Pictures Imageworks Inc., et al. 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 name of Sony Pictures Imageworks nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Boost 1.7.6 + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. + + +Minizip 1.01 + +Condition of use and distribution are the same as zlib: + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any damages +arising from the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgement in the product documentation would be + appreciated but is not required. +2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. +3. This notice may not be removed or altered from any source distribution. + +OpenImagineIO 1.5.18 + +OpenImageIO and all code, documentation, and other materials contained therein are: +Copyright 2008-2018 Larry Gritz et al. 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 name of the software's owners nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +Openjpeg + + +The copyright in this software is being made available under the 2-clauses +BSD License, included below. This software may be subject to other third +party and contributor rights, including patent rights, and no such rights +are granted under this license. + +Copyright (c) 2002-2014, Universite catholique de Louvain (UCL), Belgium +Copyright (c) 2002-2014, Professor Benoit Macq +Copyright (c) 2003-2014, Antonin Descampe +Copyright (c) 2003-2009, Francois-Olivier Devaux +Copyright (c) 2005, Herve Drolon, FreeImage Team +Copyright (c) 2002-2003, Yannick Verschueren +Copyright (c) 2001-2003, David Janssens +Copyright (c) 2011-2012, Centre National d'Etudes Spatiales (CNES), France +Copyright (c) 2012, CS Systemes d'Information, France + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS' +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +Libvorbis + +THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE. +USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS +GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE +IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. + +THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2001 +by the Xiph.Org Foundation http://www.xiph.org/ + +Libcms + +Little Color Management System +Copyright (c) 1998-2014 Marti Maria Saguer + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO +THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Yaml-cpp + +Copyright (c) 2008-2015 Jesse Beder. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +Python + + +PSF LICENSE AGREEMENT FOR PYTHON 3.7.6 +-------------------------------------------- + +1. This LICENSE AGREEMENT is between the Python Software Foundation ("PSF"), and +the Individual or Organization ("Licensee") accessing and otherwise using Python +3.7.6 software in source or binary form and its associated documentation. + +2. Subject to the terms and conditions of this License Agreement, PSF hereby +grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce, +analyze, test, perform and/or display publicly, prepare derivative works, +distribute, and otherwise use Python 3.7.6 alone or in any derivative +version, provided, however, that PSF's License Agreement and PSF's notice of +copyright, i.e., "Copyright © 2001-2020 Python Software Foundation; All Rights +Reserved" are retained in Python 3.7.6 alone or in any derivative version +prepared by Licensee. + +3. In the event Licensee prepares a derivative work that is based on or +incorporates Python 3.7.1 or any part thereof, and wants to make the +derivative work available to others as provided herein, then Licensee hereby +agrees to include in any such work a brief summary of the changes made to Python +3.7.6. + +4. PSF is making Python 3.7.6 available to Licensee on an "AS IS" basis. +PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED. BY WAY OF +EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND DISCLAIMS ANY REPRESENTATION OR +WARRANTY OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE +USE OF PYTHON 3.7.6 WILL NOT INFRINGE ANY THIRD PARTY RIGHTS. + +5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON 3.7.6 +FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS A RESULT OF +MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 3.7.6, OR ANY DERIVATIVE +THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF. + +6. This License Agreement will automatically terminate upon a material breach of +its terms and conditions. + +7. Nothing in this License Agreement shall be deemed to create any relationship +of agency, partnership, or joint venture between PSF and Licensee. This License +Agreement does not grant permission to use PSF trademarks or trade name in a +trademark sense to endorse or promote products or services of Licensee, or any +third party. + +8. By copying, installing or otherwise using Python 3.7.6, Licensee agrees +to be bound by the terms and conditions of this License Agreement. + +OpenSSL + +OpenSSL License +-------------------------------------------- + + * Copyright (c) 1998-2019 The OpenSSL Project. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in +the documentation and/or other materials provided with the +distribution. + +3. All advertising materials mentioning features or use of this +software must display the following acknowledgment: +"This product includes software developed by the OpenSSL Project +for use in the OpenSSL Toolkit. (http://www.openssl.org/)" + +4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to +endorse or promote products derived from this software without +prior written permission. For written permission, please contact +openssl-core@openssl.org. + +5. Products derived from this software may not be called "OpenSSL" +nor may "OpenSSL" appear in their names without prior written +permission of the OpenSSL Project. + +6. Redistributions of any form whatsoever must retain the following +acknowledgment: +"This product includes software developed by the OpenSSL Project +for use in the OpenSSL Toolkit (http://www.openssl.org/)" + +THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ''AS IS'' AND ANY +EXPRESSED 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 OpenSSL PROJECT OR +ITS 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. + +This product includes cryptographic software written by Eric Young +(eay@cryptsoft.com). This product includes software written by Tim +Hudson (tjh@cryptsoft.com). + +Original SSLeay License + ----------------------- + + * Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) All rights reserved. + +This package is an SSL implementation written by Eric Young (eay@cryptsoft.com). +The implementation was written so as to conform with Netscapes SSL. + +This library is free for commercial and non-commercial use as long as +the following conditions are aheared to. The following conditions +apply to all code found in this distribution, be it the RC4, RSA, +lhash, DES, etc., code; not just the SSL code. The SSL documentation +included with this distribution is covered by the same copyright terms +except that the holder is Tim Hudson (tjh@cryptsoft.com). + +Copyright remains Eric Young's, and as such any Copyright notices in +the code are not to be removed. +If this package is used in a product, Eric Young should be given attribution +as the author of the parts of the library used. +This can be in the form of a textual message at program startup or +in documentation (online or textual) provided with the package. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: +1. Redistributions of source code must retain the copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. All advertising materials mentioning features or use of this software +must display the following acknowledgement: +"This product includes cryptographic software written by +Eric Young (eay@cryptsoft.com)" +The word 'cryptographic' can be left out if the rouines from the library +being used are not cryptographic related :-). +4. If you include any Windows specific code (or a derivative thereof) from +the apps directory (application code) you must include an acknowledgement: +"This product includes software written by Tim Hudson (tjh@cryptsoft.com)" + +THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ''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 AUTHOR 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. + +The licence and distribution terms for any publically available version or +derivative of this code cannot be changed. i.e. this code cannot simply be +copied and put under another distribution licence including the GNU Public Licence. + +Libpng + +COPYRIGHT NOTICE, DISCLAIMER, and LICENSE +========================================= + +PNG Reference Library License version 2 +--------------------------------------- + + * Copyright (c) 1995-2019 The PNG Reference Library Authors. + * Copyright (c) 2018-2019 Cosmin Truta. + * Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson. + * Copyright (c) 1996-1997 Andreas Dilger. + * Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + +The software is supplied "as is", without warranty of any kind, +express or implied, including, without limitation, the warranties +of merchantability, fitness for a particular purpose, title, and +non-infringement. In no event shall the Copyright owners, or +anyone distributing the software, be liable for any damages or +other liability, whether in contract, tort or otherwise, arising +from, out of, or in connection with the software, or the use or +other dealings in the software, even if advised of the possibility +of such damage. + +Permission is hereby granted to use, copy, modify, and distribute +this software, or portions hereof, for any purpose, without fee, +subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you + must not claim that you wrote the original software. If you + use this software in a product, an acknowledgment in the product + documentation would be appreciated, but is not required. + + 2. Altered source versions must be plainly marked as such, and must + not be misrepresented as being the original software. + + 3. This Copyright notice may not be removed or altered from any + source or altered source distribution. + + +PNG Reference Library License version 1 (for libpng 0.5 through 1.6.35) +----------------------------------------------------------------------- + +libpng versions 1.0.7, July 1, 2000, through 1.6.35, July 15, 2018 are +Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson, are +derived from libpng-1.0.6, and are distributed according to the same +disclaimer and license as libpng-1.0.6 with the following individuals +added to the list of Contributing Authors: + + Simon-Pierre Cadieux + Eric S. Raymond + Mans Rullgard + Cosmin Truta + Gilles Vollant + James Yu + Mandar Sahastrabuddhe + Google Inc. + Vadim Barkov + +and with the following additions to the disclaimer: + + There is no warranty against interference with your enjoyment of + the library or against infringement. There is no warranty that our + efforts or the library will fulfill any of your particular purposes + or needs. This library is provided with all faults, and the entire + risk of satisfactory quality, performance, accuracy, and effort is + with the user. + +Some files in the "contrib" directory and some configure-generated +files that are distributed with libpng have other copyright owners, and +are released under other open source licenses. + +libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are +Copyright (c) 1998-2000 Glenn Randers-Pehrson, are derived from +libpng-0.96, and are distributed according to the same disclaimer and +license as libpng-0.96, with the following individuals added to the +list of Contributing Authors: + + Tom Lane + Glenn Randers-Pehrson + Willem van Schaik + +libpng versions 0.89, June 1996, through 0.96, May 1997, are +Copyright (c) 1996-1997 Andreas Dilger, are derived from libpng-0.88, +and are distributed according to the same disclaimer and license as +libpng-0.88, with the following individuals added to the list of +Contributing Authors: + + John Bowler + Kevin Bracey + Sam Bushell + Magnus Holmgren + Greg Roelofs + Tom Tanner + +Some files in the "scripts" directory have other copyright owners, +but are released under this license. + +libpng versions 0.5, May 1995, through 0.88, January 1996, are +Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + +For the purposes of this copyright and license, "Contributing Authors" +is defined as the following set of individuals: + + Andreas Dilger + Dave Martindale + Guy Eric Schalnat + Paul Schmidt + Tim Wegner + +The PNG Reference Library is supplied "AS IS". The Contributing +Authors and Group 42, Inc. disclaim all warranties, expressed or +implied, including, without limitation, the warranties of +merchantability and of fitness for any purpose. The Contributing +Authors and Group 42, Inc. assume no liability for direct, indirect, +incidental, special, exemplary, or consequential damages, which may +result from the use of the PNG Reference Library, even if advised of +the possibility of such damage. + +Permission is hereby granted to use, copy, modify, and distribute this +source code, or portions hereof, for any purpose, without fee, subject +to the following restrictions: + + 1. The origin of this source code must not be misrepresented. + + 2. Altered versions must be plainly marked as such and must not + be misrepresented as being the original source. + + 3. This Copyright notice may not be removed or altered from any + source or altered source distribution. + +The Contributing Authors and Group 42, Inc. specifically permit, +without fee, and encourage the use of this source code as a component +to supporting the PNG file format in commercial products. If you use +this source code in a product, acknowledgment is not required but would +be appreciated. + +Libcv, Libcxcore + + +IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. + +By downloading, copying, installing or using the software you agree to this license. +If you do not agree to this license, do not download, install, +copy or use the software. + + + Intel License Agreement + For Open Source Computer Vision Library + +Copyright (C) 2000, Intel Corporation, all rights reserved. +Third party copyrights are property of their respective owners. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistribution's of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + +* Redistribution's 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. + +* The name of Intel Corporation may not 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 Intel Corporation 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. + + +Atomic + +Copyright (c) 2003 Hewlett-Packard Development Company, L.P. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +FTGL + +Herewith is a license. Basically I want you to use this software and if +you think this license is preventing you from doing so let me know. + +Copyright (C) 2001-3 Henry Maddocks + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +Freetype + + The FreeType Project LICENSE + ---------------------------- + + 2006-Jan-27 + + Copyright 1996-2002, 2006 by + David Turner, Robert Wilhelm, and Werner Lemberg + + + +Introduction +============ + + The FreeType Project is distributed in several archive packages; + some of them may contain, in addition to the FreeType font engine, + various tools and contributions which rely on, or relate to, the + FreeType Project. + + This license applies to all files found in such packages, and + which do not fall under their own explicit license. The license + affects thus the FreeType font engine, the test programs, + documentation and makefiles, at the very least. + + This license was inspired by the BSD, Artistic, and IJG + (Independent JPEG Group) licenses, which all encourage inclusion + and use of free software in commercial and freeware products + alike. As a consequence, its main points are that: + + o We don't promise that this software works. However, we will be + interested in any kind of bug reports. (`as is' distribution) + + o You can use this software for whatever you want, in parts or + full form, without having to pay us. (`royalty-free' usage) + + o You may not pretend that you wrote this software. If you use + it, or only parts of it, in a program, you must acknowledge + somewhere in your documentation that you have used the + FreeType code. (`credits') + + We specifically permit and encourage the inclusion of this + software, with or without modifications, in commercial products. + We disclaim all warranties covering The FreeType Project and + assume no liability related to The FreeType Project. + + + Finally, many people asked us for a preferred form for a + credit/disclaimer to use in compliance with this license. We thus + encourage you to use the following text: + + """ + Portions of this software are copyright © The FreeType + Project (www.freetype.org). All rights reserved. + """ + + Please replace with the value from the FreeType version you + actually use. + + +Legal Terms +=========== + +0. Definitions +-------------- + + Throughout this license, the terms `package', `FreeType Project', + and `FreeType archive' refer to the set of files originally + distributed by the authors (David Turner, Robert Wilhelm, and + Werner Lemberg) as the `FreeType Project', be they named as alpha, + beta or final release. + + `You' refers to the licensee, or person using the project, where + `using' is a generic term including compiling the project's source + code as well as linking it to form a `program' or `executable'. + This program is referred to as `a program using the FreeType + engine'. + + This license applies to all files distributed in the original + FreeType Project, including all source code, binaries and + documentation, unless otherwise stated in the file in its + original, unmodified form as distributed in the original archive. + If you are unsure whether or not a particular file is covered by + this license, you must contact us to verify this. + + The FreeType Project is copyright (C) 1996-2000 by David Turner, + Robert Wilhelm, and Werner Lemberg. All rights reserved except as + specified below. + +1. No Warranty +-------------- + + THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO + USE, OF THE FREETYPE PROJECT. + +2. Redistribution +----------------- + + This license grants a worldwide, royalty-free, perpetual and + irrevocable right and license to use, execute, perform, compile, + display, copy, create derivative works of, distribute and + sublicense the FreeType Project (in both source and object code + forms) and derivative works thereof for any purpose; and to + authorize others to exercise some or all of the rights granted + herein, subject to the following conditions: + + o Redistribution of source code must retain this license file + (`FTL.TXT') unaltered; any additions, deletions or changes to + the original files must be clearly indicated in accompanying + documentation. The copyright notices of the unaltered, + original files must be preserved in all copies of source + files. + + o Redistribution in binary form must provide a disclaimer that + states that the software is based in part of the work of the + FreeType Team, in the distribution documentation. We also + encourage you to put an URL to the FreeType web page in your + documentation, though this isn't mandatory. + + These conditions apply to any software derived from or based on + the FreeType Project, not just the unmodified files. If you use + our work, you must acknowledge us. However, no fee need be paid + to us. + +3. Advertising +-------------- + + Neither the FreeType authors and contributors nor you shall use + the name of the other for commercial, advertising, or promotional + purposes without specific prior written permission. + + We suggest, but do not require, that you use one or more of the + following phrases to refer to this software in your documentation + or advertising materials: `FreeType Project', `FreeType Engine', + `FreeType library', or `FreeType Distribution'. + + As you have not signed this license, you are not required to + accept it. However, as the FreeType Project is copyrighted + material, only this license, or another one contracted with the + authors, grants you the right to use, distribute, and modify it. + Therefore, by using, distributing, or modifying the FreeType + Project, you indicate that you understand and accept all the terms + of this license. + +4. Contacts +----------- + + There are two mailing lists related to FreeType: + + o freetype@nongnu.org + + Discusses general use and applications of FreeType, as well as + future and wanted additions to the library and distribution. + If you are looking for support, start in this list if you + haven't found anything to help you in the documentation. + + o freetype-devel@nongnu.org + + Discusses bugs, as well as engine internals, design issues, + specific licenses, porting, etc. + + Our home page can be found at + + https://www.freetype.org + + +ffmpeg + +FFmpeg v4.4.3 + +This software uses libraries from the FFmpeg project (unmodified), which is licensed under the GNU Lesser General Public License v.2.1, which can be found at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt. + +jpeg + +This software is copyright (C) 1991-1998, Thomas G. Lane. All Rights Reserved except as specified below. + +Permission is hereby granted to use, copy, modify, and distribute this software (or portions thereof) for any purpose, without fee, subject to these conditions: + +(1) If any part of the source code for this software is distributed, then this README file must be included, with this copyright and no-warranty notice unaltered; and any additions, deletions, or changes to the original files must be clearly indicated in accompanying documentation. +(2) If only executable code is distributed, then the accompanying documentation must state that "this software is based in part on the work of the Independent JPEG Group". +(3) Permission for use of this software is granted only if the user accepts full responsibility for any undesirable consequences; the authors accept NO LIABILITY for damages of any kind. +These conditions apply to any software derived from or based on the IJG code, not just to the unmodified library. If you use our work, you ought to acknowledge us. + +Permission is NOT granted for the use of any IJG author's name or company name in advertising or publicity relating to this software or products derived from it. This software may be referred to only as "the Independent JPEG Group's software". + +We specifically permit and encourage the use of this software as the basis of commercial products, provided that all warranty or liability claims are assumed by the product vendor. + +ansi2knr.c is included in this distribution by permission of L. Peter Deutsch, sole proprietor of its copyright holder, Aladdin Enterprises of Menlo Park, CA. ansi2knr.c is NOT covered by the above copyright and conditions, but instead by the usual distribution terms of the Free Software Foundation; principally, that you must include source code if you redistribute it. (See the file ansi2knr.c for full details.) However, since ansi2knr.c is not needed as part of any program generated from the IJG code, this does not limit you more than the foregoing paragraphs do. + +The Unix configuration script "configure" was produced with GNU Autoconf. It is copyright by the Free Software Foundation but is freely distributable. The same holds for its supporting scripts (config.guess, config.sub, ltconfig, ltmain.sh). Another support script, install-sh, is copyright by M.I.T. but is also freely distributable. + +It appears that the arithmetic coding option of the JPEG spec is covered by patents owned by IBM, AT&T, and Mitsubishi. Hence arithmetic coding cannot legally be used without obtaining one or more licenses. For this reason, support for arithmetic coding has been removed from the free JPEG software. (Since arithmetic coding provides only a marginal gain over the unpatented Huffman mode, it is unlikely that very many implementations will support it.) So far as we are aware, there are no patent restrictions on the remaining code. + +The IJG distribution formerly included code to read and write GIF files. To avoid entanglement with the Unisys LZW patent, GIF reading support has been removed altogether, and the GIF writer has been simplified to produce "uncompressed GIFs". This technique does not use the LZW algorithm; the resulting GIF files are larger than usual, but are readable by all standard GIF decoders. + +We are required to state that +"The Graphics Interchange Format(c) is the Copyright property of CompuServe Incorporated. GIF(sm) is a Service Mark property of CompuServe Incorporated." + + +Libaio + +libaio is licensed under the GNU Lesser General Public License v.2.1, which can be found at https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html. + +libDevil 1.6.8 + +libDevil is licensed under the GNU Lesser General Public License v.2.1, which can be found at https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html. + +Libexif + +libexif is licensed under the GNU Lesser General Public License v.2.1, which can be found at https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html. + +Libpng + +This code is released under the libpng license. + +Copyright (c) 2018 Cosmin Truta. + +libpng versions 1.0.7, July 1, 2000 through 1.6.35, July 15, 2018 are +Copyright (c) 2000-2002, 2004, 2006-2018 Glenn Randers-Pehrson, are +derived from libpng-1.0.6, and are distributed according to the same +disclaimer and license as libpng-1.0.6 with the following individuals +added to the list of Contributing Authors: + + Simon-Pierre Cadieux + Eric S. Raymond + Mans Rullgard + Cosmin Truta + Gilles Vollant + James Yu + Mandar Sahastrabuddhe + Google Inc. + Vadim Barkov + +and with the following additions to the disclaimer: + + There is no warranty against interference with your enjoyment of + the library or against infringement. There is no warranty that our + efforts or the library will fulfill any of your particular purposes + or needs. This library is provided with all faults, and the entire + risk of satisfactory quality, performance, accuracy, and effort is + with the user. + +Some files in the "contrib" directory and some configure-generated +files that are distributed with libpng have other copyright owners, and +are released under other open source licenses. + +libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are +Copyright (c) 1998-2000 Glenn Randers-Pehrson, are derived from +libpng-0.96, and are distributed according to the same disclaimer and +license as libpng-0.96, with the following individuals added to the +list of Contributing Authors: + + Tom Lane + Glenn Randers-Pehrson + Willem van Schaik + +libpng versions 0.89, June 1996, through 0.96, May 1997, are +Copyright (c) 1996-1997 Andreas Dilger, are derived from libpng-0.88, +and are distributed according to the same disclaimer and license as +libpng-0.88, with the following individuals added to the list of +Contributing Authors: + + John Bowler + Kevin Bracey + Sam Bushell + Magnus Holmgren + Greg Roelofs + Tom Tanner + +Some files in the "scripts" directory have other copyright owners, +but are released under this license. + +libpng versions 0.5, May 1995, through 0.88, January 1996, are +Copyright (c) 1995-1996 Guy Eric Schalnat, Group 42, Inc. + +For the purposes of this copyright and license, "Contributing Authors" +is defined as the following set of individuals: + + Andreas Dilger + Dave Martindale + Guy Eric Schalnat + Paul Schmidt + Tim Wegner + +The PNG Reference Library is supplied "AS IS". The Contributing Authors +and Group 42, Inc. disclaim all warranties, expressed or implied, +including, without limitation, the warranties of merchantability and of +fitness for any purpose. The Contributing Authors and Group 42, Inc. +assume no liability for direct, indirect, incidental, special, exemplary, +or consequential damages, which may result from the use of the PNG +Reference Library, even if advised of the possibility of such damage. + +Permission is hereby granted to use, copy, modify, and distribute this +source code, or portions hereof, for any purpose, without fee, subject +to the following restrictions: + + 1. The origin of this source code must not be misrepresented. + + 2. Altered versions must be plainly marked as such and must not + be misrepresented as being the original source. + + 3. This Copyright notice may not be removed or altered from any + source or altered source distribution. + +The Contributing Authors and Group 42, Inc. specifically permit, without +fee, and encourage the use of this source code as a component to +supporting the PNG file format in commercial products. If you use this +source code in a product, acknowledgment is not required but would be +appreciated. + +END OF COPYRIGHT NOTICE, DISCLAIMER, and LICENSE. + +TRADEMARK: + +The name "libpng" has not been registered by the Copyright owner +as a trademark in any jurisdiction. However, because libpng has +been distributed and maintained world-wide, continually since 1995, +the Copyright owner claims "common-law trademark protection" in any +jurisdiction where common-law trademark is recognized. + +OSI CERTIFICATION: + +libpng is OSI Certified Open Source Software. OSI Certified Open Source +is a certification mark of the Open Source Initiative. OSI has not +addressed the additional disclaimers inserted at libpng version 1.0.7. + +EXPORT CONTROL: + +The Copyright owner believes that libpng is not subject to U.S. or +Canadian export controls, because it is open source, publicly available +software, that does not contain any encryption software. + +libpng may be subject to export control laws in other countries. + +(The Copyright owner is neither a lawyer, nor an exports officer.)© 2018 GitHub, Inc. +Terms Privacy Security Status Help Contact GitHub Pricing API Training Blog About + +libRaw 0.18.0 + +libRaw 0.18.0 (unmodified). libRaw is licensed under the GNU Lesser General Public License v.2.1, which can be found at https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html. + +Libresample v1.7 + +libresample v1.7 (unmodified). libresample is licensed under the GNU Lesser General Public License v.3, which can be found at https://www.gnu.org/licenses/lgpl-3.0.en.html. + +Pyside2 v5.15.2.1 + +Copyright (C) 2018 The Qt Company Ltd. + +Pyside2 is licensed under the GNU Lesser General Public License v.3, which can be found at https://www.gnu.org/licenses/lgpl-3.0.en.html. + +Qt 5.15.3 + +© 2019 The Qt Company. +Qt is licensed under the GNU Lesser General Public License v.3, which can be found at https://www.gnu.org/licenses/lgpl-3.0.en.html. + +Mesa3D + +Copyright (C) 1999-2007 Brian Paul All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + + +Libdav1d + +Copyright © 2018-2019, VideoLAN and dav1d authors +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Doctest 2.4.9 + +The MIT License (MIT) + +Copyright (c) 2016-2021 Viktor Kirilov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +gc + +MIT-style License + +Copyright (c) 1988-1989 Hans-J. Boehm, Alan J. Demers +Copyright (c) 1991-1996 by Xerox Corporation. All rights reserved. +Copyright (c) 1996-1999 by Silicon Graphics. All rights reserved. +Copyright (c) 1998 by Fergus Henderson. All rights reserved. +Copyright (c) 1999-2001 by Red Hat, Inc. All rights reserved. +Copyright (c) 1999-2011 Hewlett-Packard Development Company, L.P. +Copyright (c) 2004-2005 Andrei Polushin +Copyright (c) 2007 Free Software Foundation, Inc. +Copyright (c) 2008-2022 Ivan Maidanski +Copyright (c) 2011 Ludovic Courtes +Copyright (c) 2018 Petter A. Urkedal + + +THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED +OR IMPLIED. ANY USE IS AT YOUR OWN RISK. + +Permission is hereby granted to use or copy this program +for any purpose, provided the above notices are retained on all copies. +Permission to modify the code and to distribute modified code is granted, +provided the above notices are retained, and a notice that the code was +modified is included with the above copyright notice. + + +Several files (gc/gc_allocator.h, extra/msvc_dbg.c) come with slightly +different licenses, though they are all similar in spirit (the exact +licensing terms are given at the beginning of the corresponding source file). + +A few of the files needed to use the GNU-style build procedure come with +a modified GPL license that appears not to significantly restrict use of +the collector, though use of those files for a purpose other than building +the collector may require the resulting code to be covered by the GPL. + +libatomic_ops + +MIT License (core library) / GPL-2.0 (gpl extension library disabled) + +Copyright (c) 1991-1994 by Xerox Corporation. All rights reserved. +Copyright (c) 1996-1999 by Silicon Graphics. All rights reserved. +Copyright (c) 1999-2011 Hewlett-Packard Development Company, L.P. +Copyright (c) 2005, 2007 Thiemo Seufer +Copyright (c) 2007 by NEC LE-IT. All rights reserved. +Copyright (c) 2008-2022 Ivan Maidanski +Copyright (c) 2009 Bradley Smith +Copyright (c) 2009 by Takashi Yoshii. All rights reserved. + + +Our intent is to make it easy to use libatomic_ops, in both free and +proprietary software. Hence most of code (core library) that we expect to +be linked into a client application is covered by a MIT or MIT-style license. + +However, a few library routines (the gpl extension library) are covered by +the GNU General Public License. These are put into a separate library, +libatomic_ops_gpl.a file. + +Most of the test code is covered by the GNU General Public License too. + + +The low-level (core) part of the library (libatomic_ops.a) is mostly covered +by the MIT license: + +---------------------------------------- + +Copyright (c) ... + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +-------------------------------- + + +Some files in the atomic_ops/sysdeps directory (part of core library) were +inherited in part from the Boehm-Demers-Weiser conservative garbage collector, +and are covered by its license, which is similar in spirit to MIT license: + +-------------------------------- + +Copyright (c) ... + +THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED +OR IMPLIED. ANY USE IS AT YOUR OWN RISK. + +Permission is hereby granted to use or copy this program +for any purpose, provided the above notices are retained on all copies. +Permission to modify the code and to distribute modified code is granted, +provided the above notices are retained, and a notice that the code was +modified is included with the above copyright notice. + +---------------------------------- + + +A few files are covered by the GNU General Public License. (See file +"COPYING".) This applies only to the test code and the atomic_ops_gpl +portion of the library. Thus, atomic_ops_gpl should generally not be +linked into proprietary code. (This distinction was motivated by patent +considerations.) + +Glew + +The OpenGL Extension Wrangler Library +Copyright (C) 2002-2007, Milan Ikits +Copyright (C) 2002-2007, Marcelo E. Magallon +Copyright (C) 2002, Lev Povalahev +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. +* The name of the author may be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF +THE POSSIBILITY OF SUCH DAMAGE. + + +Mesa 3-D graphics library +Version: 7.0 + +Copyright (C) 1999-2007 Brian Paul All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +BRIAN PAUL BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN +AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Copyright (c) 2007 The Khronos Group Inc. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and/or associated documentation files (the +"Materials"), to deal in the Materials without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Materials, and to +permit persons to whom the Materials are furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Materials. + +THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. + + +IMath + +Copyright Contributors to the OpenEXR Project. 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 name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Eigen + +Copyright (C) 2008 Gael Guennebaud +Copyright (C) 2007-2011 Benoit Jacob + +This Source Code Form is subject to the terms of the Mozilla +Public License v. 2.0. If a copy of the MPL was not distributed +with this file, You can obtain one at http://mozilla.org/MPL/2.0/. + +prce + + Copyright (c) 1997-2007 University of Cambridge + +----------------------------------------------------------------------------- +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + * Neither the name of the University of Cambridge nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +webp + +Copyright 2011 Google Inc. All Rights Reserved. + +This code is licensed under the same terms as WebM: +Software License Agreement: http://www.webmproject.org/license/software/ +dditional IP Rights Grant: http://www.webmproject.org/license/additional/ + + +win_posix, win_pthread + +Copyright(C) 1998 John E. Bossom +Copyright(C) 1999,2005 Pthreads-win32 contributors + +Contact Email: rpj@callisto.canberra.edu.au + +The current list of contributors is contained + in the file CONTRIBUTORS included with the source + code distribution. The list can also be seen at the +following World Wide Web location: +http://sources.redhat.com/pthreads-win32/contributors.html + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public + License along with this library in the file COPYING.LIB; + if not, write to the Free Software Foundation, Inc., + 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + + +nedmalloc + +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 000000000..5b34a41a8 --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# Open RV +Open RV is an image and sequence viewer for VFX and animation artists. +Open RV is high-performant, hardware accelerated, and pipeline-friendly. + +## Documentation +[RV Documentation](doc/rv-manuals.md) + +## Build Instructions + +You can build Open RV for Linux, Windows, and macOS. You build it from scratch using the instructions specific to each OS: + +* [Linux](doc/build_system/config_linux_centos7.md) +* [Windows](doc/build_system/config_windows.md) +* [macOS](doc/build_system/config_macos.md) + +### Building Tips + +#### Build Aliases + +You can use `source rvmds.sh` to add common build aliases into your shell. After the first download following the installation of the required dependencies, use `rvbootstrap` to set up, configure, and build Open RV with the default options. + +After the setup, you can use `rvmk` to build. + +#### Compile in Parallel + +Start the compilation in parallel by adding `--parallel[=threadCound]` to the build command. This is the same as using `-j`. + +#### 3rd Parties Outside Of Repository + +If you desire to keep your third-party builds between build cleanups, set `-DRV_DEPS_BASE_DIR=/path/to/third/party`. + +#### Expert Mode + +You can always go to the build directory and call the generator directly. + +## Running Open RV + +Once the build ends, you can execute (or debug!) Open RV from the cmake-build directory. + +The path to the build is `cmake-build/stage/app`. The Open RV cmake options set up the environment so you can start the build without RPATH issues. + +## Install + +The recommended method to install Open RV is to invoke the install build step tool using cmake. + +The build system allows you to prepackage Open RV using cmake's install command and a prefix. + +Then, it's up to you to either sign or package the result, or to do both. It should contain the minimum required to have a full Open RV. + +```shell +cmake --install cmake-build --prefix /Absolute/path/to/a/destination/folder +``` + +## Run Tests + +You invoke Open RV tests with the following command: + +```shell +ctest --test-dir cmake-build --extra-verbose +``` + +### Tests Tips + +#### Run The Tests In Parallel + +You can run the tests in parallel by specifying `--parallel X`, where X is the number of tests to run in parallel. + +#### Run A Subset Of The Tests + +You can apply a filter with the `-R` flag to specify a regex. + +#### Run The Tests Verbose + +You can run the tests with extra verbosity with the flag `--extra-verbose`. + +> **Important:** You cannot use `--extra-verbose` with `--parallel`. It's one or the other, not both. + +## Contributing to Open RV + +This repository uses pre-commit to have formatting executed before a commit. To install the hooks: + +```shell +pre-commit install +``` + +> **Important:** When the hooks reformat a file, you need to re-add them to git to have your `git commit` command executed. +> Also, you can skip the hook execution by using `git commit -n`. diff --git a/cmake-format.json b/cmake-format.json new file mode 100644 index 000000000..d6a682fa3 --- /dev/null +++ b/cmake-format.json @@ -0,0 +1,11 @@ +{ + "line_width": 160, + "tab_size": 2, + "max_subgroups_hwrap": 1, + "dangle_parens": true, + "separate_ctrl_name_with_space": false, + "separate_fn_name_with_space": false, + "enable_sort": true, + "keyword_case": "upper", + "command_case": "upper" +} diff --git a/cmake/defaults/cxx_clang_defaults.cmake b/cmake/defaults/cxx_clang_defaults.cmake new file mode 100644 index 000000000..eb6edc10b --- /dev/null +++ b/cmake/defaults/cxx_clang_defaults.cmake @@ -0,0 +1,56 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +IF(RV_VERBOSE_INVOCATION) + SET(_verbose_invocation + "-v" + ) +ELSE() + SET(_verbose_invocation + "" + ) +ENDIF() + +# +# Ref.: https://clang.llvm.org/docs/CommandGuide/clang.html +IF(${RV_CPP_STANDARD} STREQUAL "14") + # Why not CMake's default gnu++14 ??? + SET(_clang_cxx_standard + "c++14" + ) +ELSEIF(${RV_CPP_STANDARD} STREQUAL "17") + # Why not CMake's default gnu++17 ??? + SET(_clang_cxx_standard + "c++17" + ) +ELSE() + MESSAGE(FATAL_ERROR "Unexpected RV_CPP_STANDARD: '${RV_CPP_STANDARD}'") +ENDIF() + +# Common options +ADD_COMPILE_OPTIONS( + ${_verbose_invocation} + -Wall + -Wnonportable-include-path + -stdlib=libc++ + # not sure we need to actually set '-std' I would assume CMake does it for us. + -std=${_clang_cxx_standard} + -msse + -msse2 + -msse3 + -mmmx +) + +IF(${CMAKE_BUILD_TYPE} STREQUAL "Release") + # Release build specific options + ADD_COMPILE_OPTIONS(-DNDEBUG -O3 # Maximum optimization + ) +ELSEIF(${CMAKE_BUILD_TYPE} STREQUAL "Debug") + # Debug build specific options + ADD_COMPILE_OPTIONS( + -DDEBUG -g # Generate debugging information + -O0 # No optimization + ) +ENDIF() diff --git a/cmake/defaults/cxx_defaults.cmake b/cmake/defaults/cxx_defaults.cmake new file mode 100644 index 000000000..c34ebb3e4 --- /dev/null +++ b/cmake/defaults/cxx_defaults.cmake @@ -0,0 +1,61 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +IF(NOT RV_CPP_STANDARD + OR RV_CPP_STANDARD STREQUAL "" +) + MESSAGE(FATAL_ERROR "The RV_CPP_STANDARD variable is not defined") +ENDIF() + +IF(NOT RV_C_STANDARD + OR RV_C_STANDARD STREQUAL "" +) + MESSAGE(FATAL_ERROR "The RV_C_STANDARD variable is not defined") +ENDIF() + +INCLUDE(rv_version) + +# specify the C/C++ standard +SET(CMAKE_CXX_STANDARD + ${RV_CPP_STANDARD} +) +SET(CMAKE_C_STANDARD + ${RV_C_STANDARD} +) +SET(CMAKE_CXX_STANDARD_REQUIRED + TRUE +) +SET(CMAKE_C_STANDARD_REQUIRED + TRUE +) + +# +# Ref.: https://cmake.org/cmake/help/latest/variable/CMAKE_LANG_COMPILER_ID.html#variable:CMAKE_%3CLANG%3E_COMPILER_ID +# +# Also consider this one: https://stackoverflow.com/a/10055571 +# + +IF(NOT DEFINED RV_MAJOR_VERSION) + MESSAGE(FATAL_ERROR "The 'RV_MAJOR_VERSION' CMake variable is not set!") +ENDIF() +IF(NOT DEFINED RV_MINOR_VERSION) + MESSAGE(FATAL_ERROR "The 'RV_MINOR_VERSION' CMake variable is not set!") +ENDIF() +IF(NOT DEFINED RV_REVISION_NUMBER) + MESSAGE(FATAL_ERROR "The 'RV_REVISION_NUMBER' CMake variable is not set!") +ENDIF() + +ADD_COMPILE_OPTIONS(-DMAJOR_VERSION=${RV_MAJOR_VERSION} -DMINOR_VERSION=${RV_MINOR_VERSION} -DREVISION_NUMBER=${RV_REVISION_NUMBER}) + +IF(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + INCLUDE(cxx_gcc_defaults) +ELSEIF(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + INCLUDE(cxx_clang_defaults) +ELSEIF(MSVC) + INCLUDE(cxx_msvc_defaults) +ELSE() + MESSAGE(FATAL_ERROR "Couldn't determine compiler identity") +ENDIF() diff --git a/cmake/defaults/cxx_gcc_defaults.cmake b/cmake/defaults/cxx_gcc_defaults.cmake new file mode 100644 index 000000000..e98d2cb49 --- /dev/null +++ b/cmake/defaults/cxx_gcc_defaults.cmake @@ -0,0 +1,40 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +INCLUDE(rv_options) + +IF(RV_VERBOSE_INVOCATION) + SET(_verbose_invocation + "-v" + ) +ELSE() + SET(_verbose_invocation + "" + ) +ENDIF() + +# Common options +ADD_COMPILE_OPTIONS( + ${_verbose_invocation} + -fPIC + -fno-schedule-insns + -fno-schedule-insns2 + -msse + -msse2 + -mmmx + -mfpmath=sse +) + +IF(${CMAKE_BUILD_TYPE} STREQUAL "Release") + # Release build specific options + ADD_COMPILE_OPTIONS(-DNDEBUG -O3 # Maximum optimization + ) +ELSEIF(${CMAKE_BUILD_TYPE} STREQUAL "Debug") + # Debug build specific options + ADD_COMPILE_OPTIONS( + -DDEBUG -g # Generate debugging information + -O0 # No optimization + ) +ENDIF() diff --git a/cmake/defaults/cxx_msvc_defaults.cmake b/cmake/defaults/cxx_msvc_defaults.cmake new file mode 100644 index 000000000..1ab8f528b --- /dev/null +++ b/cmake/defaults/cxx_msvc_defaults.cmake @@ -0,0 +1,72 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(rv_options) +INCLUDE(ProcessorCount) +PROCESSORCOUNT(_cpu_count) + +# Switches +ADD_COMPILE_OPTIONS( + -DTWK_NO_SGI_BYTE_ORDER + -DTWK_LITTLE_ENDIAN + -D__LITTLE_ENDIAN__ + -DTWK_NO_STD_MIN_MAX + -D_WINDOWS + -D_AMD64=1 + -D_WIN64=1 + -DWIN64=1 + -DWIN32=1 + -D_WIN32=1 + -D_ALLOW_ITERATOR_DEBUG_LEVEL_MISMATCH=1 + -DGC_NOT_DLL + -DTWK_USE_GLEW + -DGLEW_STATIC + -DIMPORT_GL32 + -DMINIZ_DLL=1 + -DZLIB_DLL=1 + -DZLIB_WINAPI=1 + -DOPENEXR_DLL=1 + -DIMATH_DLL=1 + -DPNG_USE_DLL=1 + -DCMS_DLL=1 + -DBOOST_ALL_NO_LIB=1 + -DBOOST_ALL_DYN_LINK=1 + -DOpenColorIO_SHARED=1 + -D_SCL_SECURE_NO_WARNINGS=1 + -J + -D_CRT_SECURE_NO_DEPRECATE=1 + -DPLATFORM_OPENGL=1 + -DNOMINMAX + -D_SILENCE_STDEXT_HASH_DEPRECATION_WARNINGS +) + +# Compiler options +ADD_COMPILE_OPTIONS( + -bigobj + -EHsc + -favor:blend + -fp:precise + -FS + -GR + -Gy + -nologo + -Qfast_transcendentals + -Zc:forScope + -Zc:sizedDealloc- + -Zi +) + +# Increasing default stack size to 8MB which would be on par with Linux and most macOS versions. Visual Studio usually sets a 1MB default stack size which is +# quite lower than what's available on macOS or Linux +# +# This is mostly to get a pass on one of the Mu test that was failing on Windows. The Ackermann Mu test was failing on Windows. The test would pass with k=7 but +# fail with k=8 value (as designed for, see the ack.mu file). On Windows Visual Studio the default is 1 MB stack size whereas this is more around 8MB on macOS +# and Linux. +ADD_LINK_OPTIONS("/STACK:8388608") + +# Enable parallel builds Note that in theory we should be able to specify just /MP here but when we do cmake sets /MP1 instead. So in order to parellize the +# build, we must set the number of processors. +ADD_DEFINITIONS(/MP${_cpu_count}) diff --git a/cmake/defaults/rv_options.cmake b/cmake/defaults/rv_options.cmake new file mode 100644 index 000000000..510b677ba --- /dev/null +++ b/cmake/defaults/rv_options.cmake @@ -0,0 +1,47 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# Debugging options +OPTION(RV_VERBOSE_INVOCATION "Show the compiler/link command invocation." OFF) +OPTION(RV_SHOW_ALL_VARIABLES "Displays all build variables." ON) + +# +# General build options +SET(RV_DEPS_BASE_DIR + "${CMAKE_BINARY_DIR}" + CACHE STRING "RV's 3rd party cache location." +) +SET(RV_DEPS_DOWNLOAD_DIR + "${RV_DEPS_BASE_DIR}/RV_DEPS_DOWNLOAD" + CACHE STRING "RV's 3rd party download cache location." +) + +IF(NOT EXISTS (${RV_DEPS_BASE_DIR})) + FILE(MAKE_DIRECTORY ${RV_DEPS_BASE_DIR}) +ENDIF() + +IF(NOT EXISTS (${RV_DEPS_DOWNLOAD_DIR})) + FILE(MAKE_DIRECTORY ${RV_DEPS_DOWNLOAD_DIR}) +ENDIF() + +SET(RV_CPP_STANDARD + "17" + CACHE STRING "RV's general C++ coding standard" +) +SET_PROPERTY( + CACHE RV_CPP_STANDARD + PROPERTY STRINGS 14 17 +) + +SET(RV_C_STANDARD + "17" + CACHE STRING "RV's general C coding standard" +) +SET_PROPERTY( + CACHE RV_C_STANDARD + PROPERTY STRINGS C99 11 17 +) diff --git a/cmake/defaults/rv_targets.cmake b/cmake/defaults/rv_targets.cmake new file mode 100644 index 000000000..666681509 --- /dev/null +++ b/cmake/defaults/rv_targets.cmake @@ -0,0 +1,130 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +SET(CMAKE_SKIP_RPATH + ON +) + +IF(APPLE) + # Darwin is the name of the mach BSD-base kernel :-) + SET(RV_TARGET_DARWIN + BOOL TRUE "Detected target is Apple's macOS" + ) + SET(RV_TARGET_STRING + "Darwin" + CACHE INTERNAL "" + ) + SET(CMAKE_OSX_ARCHITECTURES + "x86_64" + CACHE STRING "Force compilation for Intel processors." FORCE + ) + + SET(RV_OSX_EMULATION + ON + ) + SET(RV_OSX_EMULATION_ARCH + "-x86_64" + CACHE STRING "Architecture to use while building the dependencies" FORCE + ) # Set to empty string for native + + # The code makes extensive use of the 'PLATFORM_DARWIN' definition + SET(PLATFORM + "DARWIN" + CACHE STRING "Platform identifier used in Tweak Makefiles" + ) + + SET(CMAKE_MACOSX_RPATH + ON + ) + + # Get macOS version + SET(_macos_version_string + "" + ) + FIND_PROGRAM(_sw_vers sw_vers) + EXECUTE_PROCESS( + COMMAND ${_sw_vers} -productVersion + RESULT_VARIABLE _result + OUTPUT_VARIABLE _macos_version_string + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + IF(_macos_version_string STREQUAL "") + MESSAGE(FATAL_ERROR "Failed to get macOS version.") + ELSE() + MESSAGE(STATUS "Building using: macOS ${_macos_version_string}") + ENDIF() + + ADD_COMPILE_OPTIONS( + -DARCH_IA32_64 + -DPLATFORM_DARWIN + -DTWK_LITTLE_ENDIAN + -D__LITTLE_ENDIAN__ + -DPLATFORM_APPLE_MACH_BSD + -DPLATFORM_OPENGL + -DGL_SILENCE_DEPRECATION + # _XOPEN_SOURCE, Required on macOS to resolve such compiling error: + # /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/ucontext.h:51:2: error: The deprecated + # ucontext routines require _XOPEN_SOURCE to be defined error The deprecated ucontext routines require _XOPEN_SOURCE to be defined + # https://pubs.opengroup.org/onlinepubs/009695399/ + -D_XOPEN_SOURCE=600 + -D_DARWIN_C_SOURCE # was at least required to compile 'pub/cxvore' on macOS + -DOSX_VERSION=${_macos_version_string} + ) + + # + # CXXFLAGS_DARWIN += -DOSX_VERSION=\"$(shell sw_vers -productVersion)\" + # + +ELSEIF(UNIX) + MESSAGE(STATUS "Building RV for generic Linux OS") + SET(RV_TARGET_LINUX + BOOL TRUE "Detected a generic Linux OS" + ) + SET(RV_TARGET_STRING + "Linux" + CACHE INTERNAL "" + ) + # Linux target is not enough to be able to set endianess We should use Boost endian.hpp in files casing on either TWK_LITTLE_ENDIAN or TWK_BIG_ENDIAN + ADD_COMPILE_OPTIONS( + -DARCH_IA32_64 + -DPLATFORM_LINUX + -DTWK_LITTLE_ENDIAN + -D__LITTLE_ENDIAN__ + -DTWK_NO_SGI_BYTE_ORDER + -DGL_GLEXT_PROTOTYPES + -DPLATFORM_OPENGL=1 + ) + +ELSEIF(WIN32) + MESSAGE(STATUS "Building RV for Microsoft Windows") + SET(RV_TARGET_WINDOWS + BOOL TRUE "Detected target is Microsoft's Windows (64bit)" + ) + SET(RV_TARGET_STRING + "Windows" + CACHE INTERNAL "" + ) + ADD_COMPILE_OPTIONS( + -DARCH_IA32_64 + -DPLATFORM_WINDOWS + -DTWK_LITTLE_ENDIAN + -D__LITTLE_ENDIAN__ + -DTWK_NO_SGI_BYTE_ORDER + -DGL_GLEXT_PROTOTYPES + -DPLATFORM_OPENGL=1 + ) + SET(PLATFORM + "WINDOWS" + CACHE STRING "Platform identifier" + ) + SET(ARCH + "IA32_64" + CACHE STRING "CPU Architecture identifier" + ) +ELSE() + MESSAGE(FATAL_ERROR "Unsupported platform") + +ENDIF() diff --git a/cmake/dependencies/CMakeLists.txt b/cmake/dependencies/CMakeLists.txt new file mode 100644 index 000000000..011db35c8 --- /dev/null +++ b/cmake/dependencies/CMakeLists.txt @@ -0,0 +1,79 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +ADD_CUSTOM_TARGET(dependencies) +SET(RV_DEPS_LIST + "" + CACHE INTERNAL "" +) + +FIND_PACKAGE(Git QUIET) +IF(GIT_FOUND + AND EXISTS "${PROJECT_SOURCE_DIR}/.git" +) + # Update submodules as needed + OPTION(GIT_SUBMODULE "Check submodules during build" ON) + IF(GIT_SUBMODULE) + MESSAGE(STATUS "Updating submodules") + EXECUTE_PROCESS( + COMMAND ${GIT_EXECUTABLE} submodule update --init --recursive + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + RESULT_VARIABLE GIT_SUBMOD_RESULT + ) + IF(NOT GIT_SUBMOD_RESULT EQUAL "0") + MESSAGE(WARNING "Unable to init or update git submodules (${GIT_SUBMOD_RESULT})") + ENDIF() + ENDIF() +ENDIF() + +IF(NOT EXISTS "${PROJECT_SOURCE_DIR}/src/pub/CMakeLists.txt") + MESSAGE(FATAL_ERROR "Unable to find required git submodules. GIT_SUBMODULE was turned off or failed. Please update submodules and try again.") +ENDIF() + +INCLUDE(qt5) +INCLUDE(openssl) # Python3 requirement +INCLUDE(python3) +INCLUDE(boost) +INCLUDE(dav1d) +IF(RV_TARGET_WINDOWS) + INCLUDE(atomic_ops) +ENDIF() +IF(RV_TARGET_DARWIN + OR RV_TARGET_LINUX +) + INCLUDE(gc) + INCLUDE(glew) +ENDIF() +INCLUDE(imath) +INCLUDE(zlib) +INCLUDE(openexr) +INCLUDE(ffmpeg) +INCLUDE(doctest) + +LIST(REMOVE_DUPLICATES RV_DEPS_LIST) +SET(RV_DEPS_LIST + ${RV_DEPS_LIST} + CACHE INTERNAL "" +) + +MESSAGE(STATUS "Using Boost: ${RV_DEPS_BOOST_VERSION}") +MESSAGE(STATUS "Using Dav1d: ${RV_DEPS_DAV1D_VERSION}") +MESSAGE(STATUS "Using FFMPEG: ${RV_DEPS_FFMPEG_VERSION}") +IF(RV_TARGET_WINDOWS) + MESSAGE(STATUS "Using atomic_ops: ${RV_DEPS_ATOMIC_OPS_VERSION}") +ENDIF() +IF(RV_TARGET_DARWIN + OR RV_TARGET_LINUX +) + MESSAGE(STATUS "Using GC: ${RV_DEPS_GC_VERSION}") + MESSAGE(STATUS "Using GLEW: ${RV_DEPS_GLEW_VERSION}") +ENDIF() +MESSAGE(STATUS "Using Imath: ${RV_DEPS_IMATH_VERSION}") +MESSAGE(STATUS "Using OpenEXR: ${RV_DEPS_OPENEXR_VERSION}") +MESSAGE(STATUS "Using Python3: ${RV_DEPS_PYTHON3_VERSION}") +MESSAGE(STATUS "Using PySide2: ${RV_DEPS_PYSIDE2_VERSION}") +MESSAGE(STATUS "Using Qt5: ${RV_DEPS_QT5_VERSION}") +MESSAGE(STATUS "Using zlib: ${RV_DEPS_ZLIB_VERSION}") diff --git a/cmake/dependencies/atomic_ops.cmake b/cmake/dependencies/atomic_ops.cmake new file mode 100644 index 000000000..70c0e7362 --- /dev/null +++ b/cmake/dependencies/atomic_ops.cmake @@ -0,0 +1,140 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(ProcessorCount) # require CMake 3.15+ +PROCESSORCOUNT(_cpu_count) + +SET(_target + "RV_DEPS_ATOMIC_OPS" +) + +SET(_version + "7.7.0" +) + +# Download the latest version because the feature we need (--disable-gpl) +# has not been released nor tagged yet +# SET(_download_url +# "https://github.com/ivmai/libatomic_ops/archive/refs/tags/v${_version}.zip" +# ) +SET(_download_url + "https://github.com/ivmai/libatomic_ops/archive/refs/heads/master.zip" +) + +SET(_download_hash + 8da38242d61d746e070e021a735798b9 +) + +SET(_install_dir + ${RV_DEPS_BASE_DIR}/${_target}/install +) + +SET(_lib_dir + ${_install_dir}/lib +) + +IF(RV_TARGET_WINDOWS) + SET(_atomic_ops_lib_name + libatomic_ops.a + ) +ELSE() + SET(_atomic_ops_lib_name + ${CMAKE_STATIC_LIBRARY_PREFIX}atomic_ops${CMAKE_STATIC_LIBRARY_SUFFIX} + ) +ENDIF() + +SET(_atomic_ops_lib + ${_lib_dir}/${_atomic_ops_lib_name} +) + +SET(_make_command + make +) +SET(_configure_command + sh ./configure +) +SET(_autogen_command + sh ./autogen.sh +) + +IF(${RV_OSX_EMULATION}) + SET(_darwin_x86_64 + "arch" "${RV_OSX_EMULATION_ARCH}" + ) + + SET(_make_command + ${_darwin_x86_64} ${_make_command} + ) + SET(_configure_command + ${_darwin_x86_64} ${_configure_command} + ) + SET(_autogen_command + ${_darwin_x86_64} ${_autogen_command} + ) +ENDIF() + +# Make sure NOT to enable GPL +SET(_configure_args + "--disable-gpl" +) +LIST(APPEND _configure_args "--prefix=${_install_dir}") + +IF(RV_TARGET_MACOS) + LIST(APPEND _configure_args MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}) +ENDIF() + +EXTERNALPROJECT_ADD( + ${_target} + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + INSTALL_DIR ${_install_dir} + URL ${_download_url} + URL_MD5 ${_download_hash} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + CONFIGURE_COMMAND ${_autogen_command} && ${_configure_command} ${_configure_args} + BUILD_COMMAND ${_make_command} -j${_cpu_count} + INSTALL_COMMAND ${_make_command} install + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_atomic_ops_lib} + USES_TERMINAL_BUILD TRUE +) + +ADD_LIBRARY(atomic_ops::atomic_ops STATIC IMPORTED GLOBAL) +ADD_DEPENDENCIES(atomic_ops::atomic_ops ${_target}) +SET_PROPERTY( + TARGET atomic_ops::atomic_ops + PROPERTY IMPORTED_LOCATION ${_atomic_ops_lib} +) + +SET(_include_dir + ${_install_dir}/include +) +FILE(MAKE_DIRECTORY ${_include_dir}) +TARGET_INCLUDE_DIRECTORIES( + atomic_ops::atomic_ops + INTERFACE ${_include_dir} +) +LIST(APPEND RV_DEPS_LIST atomic_ops::atomic_ops) + +ADD_CUSTOM_COMMAND( + COMMENT "Installing ${_target}'s libs into ${RV_STAGE_LIB_DIR}" + OUTPUT ${RV_STAGE_LIB_DIR}/${_atomic_ops_lib_name} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + DEPENDS ${_target} +) + +ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_LIB_DIR}/${_atomic_ops_lib_name} +) + +ADD_DEPENDENCIES(dependencies ${_target}-stage-target) + +SET(RV_DEPS_ATOMIC_OPS_VERSION + ${_version} + CACHE INTERNAL "" FORCE +) diff --git a/cmake/dependencies/boost.cmake b/cmake/dependencies/boost.cmake new file mode 100644 index 000000000..3829bbac7 --- /dev/null +++ b/cmake/dependencies/boost.cmake @@ -0,0 +1,295 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(ProcessorCount) # require CMake 3.15+ +PROCESSORCOUNT(_cpu_count) + +# This should handle fetching or checking then compiling required 3rd party dependencies +SET(_target + "RV_DEPS_BOOST" +) + +# This version of boost resolves Python3 compatibilty issues on Big Sur and Monterey and is compatible with Python 2.7 through Python 3.10 +SET(_version + "1.76.0" +) + +SET(_major_minor_version + "1_76" +) + +STRING(REPLACE "." "_" _version_with_underscore ${_version}) +SET(_download_url + "https://boostorg.jfrog.io/artifactory/main/release/${_version}/source/boost_${_version_with_underscore}.tar.gz" +) + +SET(_download_hash + e425bf1f1d8c36a3cd464884e74f007a +) + +SET(_install_dir + ${RV_DEPS_BASE_DIR}/${_target}/install +) + +SET(_boost_libs + atomic + chrono + container + context + contract + coroutine + date_time + filesystem + graph + iostreams + locale + log + log_setup + numpy39 + prg_exec_monitor + program_options + python39 + random + regex + serialization + stacktrace_addr2line + stacktrace_basic + stacktrace_noop + system + thread + timer + type_erasure + unit_test_framework + wave + wserialization +) + +SET(_lib_dir + ${_install_dir}/lib +) + +# Note: Boost has a custom lib naming scheme on windows +IF(RV_TARGET_WINDOWS) + SET(BOOST_SHARED_LIBRARY_PREFIX + "" + ) + IF(CMAKE_BUILD_TYPE MATCHES "^Debug$") + SET(BOOST_LIBRARY_SUFFIX + "-vc142-mt-gd-x64-${_major_minor_version}" + ) + ELSE() + SET(BOOST_LIBRARY_SUFFIX + "-vc142-mt-x64-${_major_minor_version}" + ) + ENDIF() + SET(BOOST_SHARED_LIBRARY_SUFFIX + ${BOOST_LIBRARY_SUFFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + SET(BOOST_IMPORT_LIBRARY_PREFIX + "" + ) + SET(BOOST_IMPORT_LIBRARY_SUFFIX + ${BOOST_LIBRARY_SUFFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX} + ) +ELSE() + SET(BOOST_SHARED_LIBRARY_PREFIX + ${CMAKE_SHARED_LIBRARY_PREFIX} + ) + SET(BOOST_SHARED_LIBRARY_SUFFIX + ${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ENDIF() + +FOREACH( + _boost_lib + ${_boost_libs} +) + SET(_boost_${_boost_lib}_lib_name + ${BOOST_SHARED_LIBRARY_PREFIX}boost_${_boost_lib}${BOOST_SHARED_LIBRARY_SUFFIX} + ) + SET(_boost_${_boost_lib}_lib + ${_lib_dir}/${_boost_${_boost_lib}_lib_name} + ) + LIST(APPEND _boost_byproducts ${_boost_${_boost_lib}_lib}) + IF(RV_TARGET_WINDOWS) + SET(_boost_${_boost_lib}_implib + ${_lib_dir}/${BOOST_IMPORT_LIBRARY_PREFIX}boost_${_boost_lib}${BOOST_IMPORT_LIBRARY_SUFFIX} + ) + LIST(APPEND _boost_byproducts ${_boost_${_boost_lib}_implib}) + ENDIF() +ENDFOREACH() + +SET(_boost_b2_options + "-s NO_LZMA=1" +) +IF(RV_VERBOSE_INVOCATION) + SET(_boost_b2_options + "${_boost_b2_options} -d+2" + ) +ELSE() + SET(_boost_b2_options + "${_boost_b2_options} -d+0" + ) +ENDIF() + +IF(RV_TARGET_DARWIN) + SET(_toolset + "clang" + ) + SET(_boost_b2_options + "-mmacosx-version-min=${CMAKE_OSX_DEPLOYMENT_TARGET}" + ) +ELSEIF(RV_TARGET_LINUX) + SET(_toolset + "gcc" + ) +ELSEIF(RV_TARGET_WINDOWS) + SET(_toolset + "msvc-14.2" + ) +ELSE() + MESSAGE(FATAL_ERROR "Unsupported (yet) target for Boost") +ENDIF() + +SET(_b2_command + ./b2 +) + +IF(RV_TARGET_WINDOWS) + SET(_bootstrap_command + ./bootstrap.bat + ) +ELSE() + SET(_bootstrap_command + ./bootstrap.sh + ) +ENDIF() + +IF(${RV_OSX_EMULATION}) + SET(_darwin_x86_64 + "arch" "${RV_OSX_EMULATION_ARCH}" + ) + + SET(_b2_command + ${_darwin_x86_64} ${_b2_command} + ) + SET(_bootstrap_command + ${_darwin_x86_64} ${_bootstrap_command} + ) +ENDIF() + +IF(RV_TARGET_WINDOWS) + SET(_boost_python_bin + ${RV_DEPS_BASE_DIR}/RV_DEPS_PYTHON3/install/python.exe + ) +ELSE() + SET(_boost_python_bin + ${RV_DEPS_BASE_DIR}/RV_DEPS_PYTHON3/install/bin/python + ) +ENDIF() + +STRING(TOLOWER ${CMAKE_BUILD_TYPE} _boost_variant) + +EXTERNALPROJECT_ADD( + ${_target} + DEPENDS Python::Python + DOWNLOAD_NAME ${_target}_${_version}.tar.gz + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + INSTALL_DIR ${_install_dir} + URL ${_download_url} + URL_MD5 ${_download_hash} + CONFIGURE_COMMAND ${_bootstrap_command} --with-toolset=${_toolset} --with-python=${_boost_python_bin} + BUILD_COMMAND + # Ref.: https://www.boost.org/doc/libs/1_70_0/tools/build/doc/html/index.html#bbv2.builtin.features.cflags Ref.: + # https://www.boost.org/doc/libs/1_76_0/tools/build/doc/html/index.html#bbv2.builtin.features.cflags + ./b2 -a -q toolset=${_toolset} cxxstd=${RV_CPP_STANDARD} variant=${_boost_variant} link=shared threading=multi architecture=x86 address-model=64 + --with-chrono --with-date_time --with-filesystem --with-graph --with-iostreams --with-locale --with-program_options --with-random --with-regex + --with-serialization --with-system --with-thread --with-timer ${_boost_b2_options} -j${_cpu_count} install --prefix=${_install_dir} + INSTALL_COMMAND echo "Boost was both built and installed in the build stage" + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_boost_byproducts} + USES_TERMINAL_BUILD TRUE +) + +IF(RV_TARGET_WINDOWS) + SET(_include_dir + ${_install_dir}/include/boost-${_major_minor_version} + ) +ELSE() + SET(_include_dir + ${_install_dir}/include + ) +ENDIF() + +FILE(MAKE_DIRECTORY ${_include_dir}) + +FOREACH( + _boost_lib + ${_boost_libs} +) + ADD_LIBRARY(Boost::${_boost_lib} SHARED IMPORTED GLOBAL) + ADD_DEPENDENCIES(Boost::${_boost_lib} ${_target}) + SET_PROPERTY( + TARGET Boost::${_boost_lib} + PROPERTY IMPORTED_LOCATION ${_boost_${_boost_lib}_lib} + ) + SET_PROPERTY( + TARGET Boost::${_boost_lib} + PROPERTY IMPORTED_SONAME ${_boost_${_boost_lib}_lib_name} + ) + + IF(RV_TARGET_WINDOWS) + SET_PROPERTY( + TARGET Boost::${_boost_lib} + PROPERTY IMPORTED_IMPLIB ${_boost_${_boost_lib}_implib} + ) + ENDIF() + TARGET_INCLUDE_DIRECTORIES( + Boost::${_boost_lib} + INTERFACE ${_include_dir} + ) + + LIST(APPEND RV_DEPS_LIST Boost::${_boost_lib}) + LIST(APPEND _boost_stage_output ${RV_STAGE_LIB_DIR}/${_boost_${_boost_lib}_lib_name}) +ENDFOREACH() + +ADD_LIBRARY(Boost::headers INTERFACE IMPORTED GLOBAL) +SET_TARGET_PROPERTIES( + Boost::headers + PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${_include_dir}" +) + +IF(RV_TARGET_WINDOWS) + ADD_CUSTOM_COMMAND( + TARGET ${_target} + POST_BUILD + COMMENT "Installing ${_target}'s libs and bin into ${RV_STAGE_LIB_DIR} and ${RV_STAGE_BIN_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_BIN_DIR} + ) +ELSE() + ADD_CUSTOM_COMMAND( + COMMENT "Installing ${_target}'s libs into ${RV_STAGE_LIB_DIR}" + OUTPUT ${_boost_stage_output} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + DEPENDS ${_target} + ) +ENDIF() + +ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${_boost_stage_output} +) + +ADD_DEPENDENCIES(dependencies ${_target}-stage-target) + +SET(RV_DEPS_BOOST_VERSION + ${_version} + CACHE INTERNAL "" FORCE +) diff --git a/cmake/dependencies/dav1d.cmake b/cmake/dependencies/dav1d.cmake new file mode 100644 index 000000000..e8ffbdb98 --- /dev/null +++ b/cmake/dependencies/dav1d.cmake @@ -0,0 +1,148 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(ProcessorCount) # require CMake 3.15+ +PROCESSORCOUNT(_cpu_count) + +SET(_target + "RV_DEPS_DAV1D" +) + +SET(_version + "1.0.0" +) + +SET(_download_url + "https://github.com/videolan/dav1d/archive/refs/tags/${_version}.zip" +) +SET(_download_hash + "425282a997804984c5c115aacb005ab4" +) + +SET(_install_dir + ${RV_DEPS_BASE_DIR}/${_target}/install +) +SET(_include_dir + ${_install_dir}/include +) +IF(RV_TARGET_LINUX) + SET(_lib_dir + ${_install_dir}/lib64 + ) +ELSE() + SET(_lib_dir + ${_install_dir}/lib + ) +ENDIF() + +SET(_david_lib_name + ${CMAKE_STATIC_LIBRARY_PREFIX}dav1d${CMAKE_STATIC_LIBRARY_SUFFIX} +) + +SET(_dav1d_lib + ${_lib_dir}/${_david_lib_name} +) + +# Required for configuring ffmpeg build +SET(RV_DEPS_DAVID_LIB_DIR + ${_lib_dir} + CACHE INTERNAL "" +) + +SET(_configure_command + meson +) +SET(_make_command + ninja +) + +IF(${RV_OSX_EMULATION}) + SET(_meson_cross_file + "${CMAKE_SOURCE_DIR}/src/build/meson_arch_x86_64.txt" + ) + SET(_configure_command + ${_configure_command} "--cross-file" ${_meson_cross_file} + ) +ENDIF() + +IF(RV_TARGET_WINDOWS) + SET(_default_library + shared + ) +ELSE() + SET(_default_library + static + ) +ENDIF() + +EXTERNALPROJECT_ADD( + ${_target} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + INSTALL_DIR ${_install_dir} + URL ${_download_url} + URL_MD5 ${_download_hash} + CONFIGURE_COMMAND ${_configure_command} ./_build --default-library=${_default_library} --prefix=${_install_dir} -Denable_tests=false -Denable_tools=false + BUILD_COMMAND ${_make_command} -C _build + INSTALL_COMMAND ${_make_command} -C _build install + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_david_lib} + USES_TERMINAL_BUILD TRUE +) + +ADD_LIBRARY(dav1d::dav1d STATIC IMPORTED GLOBAL) +ADD_DEPENDENCIES(dav1d::dav1d ${_target}) +SET_PROPERTY( + TARGET dav1d::dav1d + PROPERTY IMPORTED_LOCATION ${_dav1d_lib} +) +SET_PROPERTY( + TARGET dav1d::dav1d + PROPERTY IMPORTED_SONAME ${_david_lib_name} +) + +FILE(MAKE_DIRECTORY ${_include_dir}) +TARGET_INCLUDE_DIRECTORIES( + dav1d::dav1d + INTERFACE ${_include_dir} +) +LIST(APPEND RV_DEPS_LIST dav1d::dav1d) + +IF(RV_TARGET_WINDOWS) + ADD_CUSTOM_COMMAND( + TARGET ${_target} + POST_BUILD + COMMENT "Installing ${_target}'s libs and bin into ${RV_STAGE_LIB_DIR} and ${RV_STAGE_BIN_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/lib ${RV_STAGE_LIB_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/bin ${RV_STAGE_BIN_DIR} + ) + ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_LIB_DIR}/${_david_lib_name} + ) +ELSE() + ADD_CUSTOM_COMMAND( + COMMENT "Installing ${_target}'s libs into ${RV_STAGE_LIB_DIR}" + OUTPUT ${RV_STAGE_LIB_DIR}/${_david_lib_name} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + DEPENDS ${_target} + ) + ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_LIB_DIR}/${_david_lib_name} + ) +ENDIF() + +ADD_DEPENDENCIES(dependencies ${_target}-stage-target) + +SET(RV_DEPS_DAV1D_VERSION + ${_version} + CACHE INTERNAL "" FORCE +) diff --git a/cmake/dependencies/doctest.cmake b/cmake/dependencies/doctest.cmake new file mode 100644 index 000000000..cc0f4e8ad --- /dev/null +++ b/cmake/dependencies/doctest.cmake @@ -0,0 +1,51 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +SET(_target + "RV_DEPS_DOCTEST" +) + +SET(_version + "v2.4.9" +) + +SET(_download_url + "https://github.com/doctest/doctest/archive/refs/tags/${_version}.tar.gz" +) +SET(_download_hash + "a7948b5ec1f69de6f84c7d7487aaf79b" +) + +SET(_install_dir + ${RV_DEPS_BASE_DIR}/${_target}/install +) +SET(_include_dir + ${_install_dir}/include +) + +# This is an include-only archive, we don't want to build anything +EXTERNALPROJECT_ADD( + ${_target} + PREFIX ${RV_DEPS_BASE_DIR}/${_target} + DOWNLOAD_NAME ${_target}_${_version}.tar.gz + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + URL ${_download_url} + URL_MD5 ${_download_hash} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + BUILD_ALWAYS FALSE + TIMEOUT 10 + LOG_DOWNLOAD ON +) + +ADD_LIBRARY(doctest INTERFACE) +ADD_DEPENDENCIES(doctest ${_target}) +TARGET_INCLUDE_DIRECTORIES( + doctest + INTERFACE "${RV_DEPS_BASE_DIR}/${_target}/src/${_target}" +) diff --git a/cmake/dependencies/ffmpeg.cmake b/cmake/dependencies/ffmpeg.cmake new file mode 100644 index 000000000..d96039e6e --- /dev/null +++ b/cmake/dependencies/ffmpeg.cmake @@ -0,0 +1,292 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(ProcessorCount) # require CMake 3.15+ +PROCESSORCOUNT(_cpu_count) + +SET(_target + "RV_DEPS_FFMPEG" +) + +SET(_version + "n4.4.3" +) +SET(_download_url + "https://github.com/FFmpeg/FFmpeg/archive/refs/tags/${_version}.zip" +) + +SET(_download_hash + "51ffa9de9e5b0c17cbabc0d8b780beb2" +) + +SET(_install_dir + ${RV_DEPS_BASE_DIR}/${_target}/install +) + +LIST(APPEND _disabled_decoders "--disable-decoder=bink") +LIST(APPEND _disabled_decoders "--disable-decoder=binkaudio_dct") +LIST(APPEND _disabled_decoders "--disable-decoder=binkaudio_rdft") +LIST(APPEND _disabled_decoders "--disable-decoder=vp9") +LIST(APPEND _disabled_decoders "--disable-decoder=vp9_cuvid") +LIST(APPEND _disabled_decoders "--disable-decoder=vp9_mediacodec") +LIST(APPEND _disabled_decoders "--disable-decoder=vp9_qsv") +LIST(APPEND _disabled_decoders "--disable-decoder=vp9_rkmpp") +LIST(APPEND _disabled_decoders "--disable-decoder=vp9_v4l2m2m") +LIST(APPEND _disabled_decoders "--disable-decoder=dnxhd") +LIST(APPEND _disabled_decoders "--disable-decoder=prores") +LIST(APPEND _disabled_decoders "--disable-decoder=qtrle") +LIST(APPEND _disabled_decoders "--disable-decoder=aac") +LIST(APPEND _disabled_decoders "--disable-decoder=aac_fixed") +LIST(APPEND _disabled_decoders "--disable-decoder=aac_latm") +LIST(APPEND _disabled_decoders "--disable-decoder=dvvideo") + +LIST(APPEND _disabled_encoders "--disable-encoder=dnxhd") +LIST(APPEND _disabled_encoders "--disable-encoder=prores") +LIST(APPEND _disabled_encoders "--disable-encoder=qtrle") +LIST(APPEND _disabled_encoders "--disable-encoder=aac") +LIST(APPEND _disabled_encoders "--disable-encoder=aac_mf") +LIST(APPEND _disabled_encoders "--disable-encoder=vp9_qsv") +LIST(APPEND _disabled_encoders "--disable-encoder=vp9_vaapi") +LIST(APPEND _disabled_decoders "--disable-encoder=dvvideo") + +LIST(APPEND _disabled_parsers "--disable-parser=vp9") + +LIST(APPEND _disabled_filters "--disable-filter=geq") + +LIST(APPEND _disabled_protocols "--disable-protocol=ffrtmpcrypt") +LIST(APPEND _disabled_protocols "--disable-protocol=rtmpe") +LIST(APPEND _disabled_protocols "--disable-protocol=rtmpte") + +SET(_make_command + make +) +SET(_configure_command + sh ./configure +) + +IF(${RV_OSX_EMULATION}) + SET(_darwin_x86_64 + "arch" "${RV_OSX_EMULATION_ARCH}" + ) + + SET(_make_command + ${_darwin_x86_64} ${_make_command} + ) + SET(_configure_command + ${_darwin_x86_64} ${_configure_command} + ) +ENDIF() + +SET(_include_dir + ${_install_dir}/include +) +IF(RV_TARGET_WINDOWS) + SET(_lib_dir + ${_install_dir}/bin + ) +ELSE() + SET(_lib_dir + ${_install_dir}/lib + ) +ENDIF() + +IF(RV_TARGET_DARWIN) + SET(_ffmpeg_avutil_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}avutil.56${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + SET(_ffmpeg_swresample_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}swresample.3${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + SET(_ffmpeg_swscale_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}swscale.5${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + SET(_ffmpeg_avcodec_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}avcodec.58${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + SET(_ffmpeg_avformat_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}avformat.58${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ELSEIF(RV_TARGET_LINUX) + SET(_ffmpeg_avutil_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}avutil${CMAKE_SHARED_LIBRARY_SUFFIX}.56 + ) + SET(_ffmpeg_swresample_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}swresample${CMAKE_SHARED_LIBRARY_SUFFIX}.3 + ) + SET(_ffmpeg_swscale_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}swscale${CMAKE_SHARED_LIBRARY_SUFFIX}.5 + ) + SET(_ffmpeg_avcodec_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}avcodec${CMAKE_SHARED_LIBRARY_SUFFIX}.58 + ) + SET(_ffmpeg_avformat_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}avformat${CMAKE_SHARED_LIBRARY_SUFFIX}.58 + ) +ELSEIF(RV_TARGET_WINDOWS) + SET(_ffmpeg_avutil_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}avutil-56${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + SET(_ffmpeg_swresample_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}swresample-3${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + SET(_ffmpeg_swscale_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}swscale-5${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + SET(_ffmpeg_avcodec_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}avcodec-58${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + SET(_ffmpeg_avformat_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}avformat-58${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ENDIF() + +SET(_ffmpeg_libs + avutil swresample swscale avcodec avformat +) + +FOREACH( + _ffmpeg_lib + ${_ffmpeg_libs} +) + SET(_ffmpeg_${_ffmpeg_lib}_lib + ${_lib_dir}/${_ffmpeg_${_ffmpeg_lib}_lib_name} + ) + LIST(APPEND _build_byproducts ${_ffmpeg_${_ffmpeg_lib}_lib}) + IF(RV_TARGET_WINDOWS) + SET(_ffmpeg_${_ffmpeg_lib}_implib + ${_lib_dir}/${CMAKE_IMPORT_LIBRARY_PREFIX}${_ffmpeg_lib}${CMAKE_IMPORT_LIBRARY_SUFFIX} + ) + LIST(APPEND _build_byproducts ${_ffmpeg_${_ffmpeg_lib}_implib}) + ENDIF() +ENDFOREACH() + +SET(_cflags + "-I${RV_DEPS_BASE_DIR}/RV_DEPS_OPENSSL/install/include" +) +IF(RV_TARGET_WINDOWS) + SET(_ldflags + "-LIBPATH:${RV_DEPS_OPENSSL_LIB_DIR} -LIBPATH:${RV_DEPS_DAVID_LIB_DIR}" + ) + SET(_toolchain + "--toolchain=msvc" + ) +ELSE() + SET(_ldflags + "-L${RV_DEPS_OPENSSL_LIB_DIR}" + ) +ENDIF() + +EXTERNALPROJECT_ADD( + ${_target} + DEPENDS dav1d::dav1d RV_DEPS_OPENSSL + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + INSTALL_DIR ${_install_dir} + URL ${_download_url} + URL_MD5 ${_download_hash} + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + CONFIGURE_COMMAND + ${CMAKE_COMMAND} -E env PKG_CONFIG_PATH=${RV_DEPS_DAVID_LIB_DIR}/pkgconfig ${_configure_command} --prefix=${_install_dir} --disable-programs --enable-shared + --enable-openssl --enable-libdav1d --disable-iconv --disable-outdevs ${_toolchain} --extra-ldflags=${_ldflags} --extra-cflags=${_cflags} + ${_disabled_decoders} ${_disabled_encoders} ${_disabled_filters} ${_disabled_parsers} ${_disabled_protocols} + BUILD_COMMAND ${_make_command} -j${_cpu_count} -v + INSTALL_COMMAND ${_make_command} install + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_build_byproducts} + USES_TERMINAL_BUILD TRUE +) + +# The enable-openssl config option expects the openssl names not to prefixed with lib, but our build of OpenSSL does add this prefix, so we'll make a copy of +# the implibs to make it work correctly +IF(RV_TARGET_WINDOWS) + EXTERNALPROJECT_ADD_STEP( + ${_target} copy_implibs + COMMAND ${CMAKE_COMMAND} -E copy ${RV_DEPS_OPENSSL_LIB_DIR}/libssl.lib ${RV_DEPS_OPENSSL_LIB_DIR}/ssl.lib + COMMAND ${CMAKE_COMMAND} -E copy ${RV_DEPS_OPENSSL_LIB_DIR}/libcrypto.lib ${RV_DEPS_OPENSSL_LIB_DIR}/crypto.lib + DEPENDERS configure + ) +ENDIF() + +FILE(MAKE_DIRECTORY ${_include_dir}) + +FOREACH( + _ffmpeg_lib + ${_ffmpeg_libs} +) + ADD_LIBRARY(ffmpeg::${_ffmpeg_lib} SHARED IMPORTED GLOBAL) + ADD_DEPENDENCIES(ffmpeg::${_ffmpeg_lib} ${_target}) + SET_PROPERTY( + TARGET ffmpeg::${_ffmpeg_lib} + PROPERTY IMPORTED_LOCATION ${_ffmpeg_${_ffmpeg_lib}_lib} + ) + IF(RV_TARGET_WINDOWS) + SET_PROPERTY( + TARGET ffmpeg::${_ffmpeg_lib} + PROPERTY IMPORTED_IMPLIB ${_ffmpeg_${_ffmpeg_lib}_implib} + ) + ENDIF() + TARGET_INCLUDE_DIRECTORIES( + ffmpeg::${_ffmpeg_lib} + INTERFACE ${_include_dir} + ) + + LIST(APPEND RV_DEPS_LIST ffmpeg::${_ffmpeg_lib}) +ENDFOREACH() + +TARGET_LINK_LIBRARIES( + ffmpeg::swresample + INTERFACE ffmpeg::avutil +) +TARGET_LINK_LIBRARIES( + ffmpeg::swscale + INTERFACE ffmpeg::avutil +) +TARGET_LINK_LIBRARIES( + ffmpeg::avcodec + INTERFACE ffmpeg::swresample +) +TARGET_LINK_LIBRARIES( + ffmpeg::avformat + INTERFACE ffmpeg::avcodec +) + +SET(${_target}-stage-flag + ${RV_STAGE_LIB_DIR}/${_target}-stage-flag +) + +IF(RV_TARGET_WINDOWS) + ADD_CUSTOM_COMMAND( + TARGET ${_target} + POST_BUILD + COMMENT "Installing ${_target}'s libs and bin into ${RV_STAGE_LIB_DIR} and ${RV_STAGE_BIN_DIR}" + # Note: The FFmpeg build stores both the import lib and the dll in the install bin directory + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/bin ${RV_STAGE_LIB_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/bin ${RV_STAGE_BIN_DIR} + COMMAND cmake -E touch ${${_target}-stage-flag} + ) +ELSE() + ADD_CUSTOM_COMMAND( + COMMENT "Installing ${_target}'s libs into ${RV_STAGE_LIB_DIR}" + OUTPUT ${${_target}-stage-flag} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + COMMAND cmake -E touch ${${_target}-stage-flag} + DEPENDS ${_target} + ) +ENDIF() + +ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${${_target}-stage-flag} +) + +ADD_DEPENDENCIES(dependencies ${_target}-stage-target) + +SET(RV_DEPS_FFMPEG_VERSION + ${_version} + CACHE INTERNAL "" FORCE +) diff --git a/cmake/dependencies/gc.cmake b/cmake/dependencies/gc.cmake new file mode 100644 index 000000000..c852ee2c5 --- /dev/null +++ b/cmake/dependencies/gc.cmake @@ -0,0 +1,217 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(ProcessorCount) # require CMake 3.15+ +PROCESSORCOUNT(_cpu_count) + +SET(_target + "RV_DEPS_GC" +) + +SET(_version + "8.2.2" +) + +SET(_download_url + "https://github.com/ivmai/bdwgc/archive/refs/tags/v${_version}.zip" +) +SET(_download_hash + "2ca38d05e1026b3426cf6c24ca3a7787" +) + +SET(_install_dir + ${RV_DEPS_BASE_DIR}/${_target}/install +) + +SET(_make_command + make +) +SET(_autogen_command + sh ./autogen.sh +) +SET(_configure_command + sh ./configure +) + +IF(${RV_OSX_EMULATION}) + SET(_darwin_x86_64 + "arch" "${RV_OSX_EMULATION_ARCH}" + ) + + SET(_make_command + ${_darwin_x86_64} ${_make_command} + ) + SET(_autogen_command + ${_darwin_x86_64} ${_autogen_command} + ) + SET(_configure_command + ${_darwin_x86_64} ${_configure_command} + ) +ENDIF() +IF(RV_TARGET_WINDOWS) + # MSYS2/CMake defaults to Ninja + SET(_make_command + ninja + ) +ENDIF() + +SET(_lib_dir + ${_install_dir}/lib +) + +SET(_gc_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}gc.1${CMAKE_SHARED_LIBRARY_SUFFIX} +) +SET(_cord_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}cord.1${CMAKE_SHARED_LIBRARY_SUFFIX} +) + +IF(RV_TARGET_DARWIN) + SET(_gc_lib + ${_lib_dir}/${CMAKE_SHARED_LIBRARY_PREFIX}gc.1${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + SET(_cord_lib + ${_lib_dir}/${CMAKE_SHARED_LIBRARY_PREFIX}cord.1${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ELSEIF(RV_TARGET_LINUX) + SET(_gc_lib + ${_lib_dir}/${CMAKE_SHARED_LIBRARY_PREFIX}gc${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + SET(_cord_lib + ${_lib_dir}/${CMAKE_SHARED_LIBRARY_PREFIX}cord${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ELSEIF(RV_TARGET_WINDOWS) + SET(_gc_lib_name + gc-lib.lib + ) + SET(_gc_lib + ${_lib_dir}/${_gc_lib_name} + ) +ENDIF() + +LIST(APPEND _gc_byproducts ${_gc_lib}) +LIST(APPEND _gc_stage_outputs ${RV_STAGE_LIB_DIR}/${_gc_lib_name}) +IF(NOT RV_TARGET_WINDOWS) + LIST(APPEND _gc_byproducts ${_cord_lib}) + LIST(APPEND _gc_stage_outputs ${RV_STAGE_LIB_DIR}/${_cord_lib_name}) +ENDIF() + +SET(_include_dir + ${_install_dir}/include +) +FILE(MAKE_DIRECTORY ${_include_dir}) + +IF(RV_TARGET_WINDOWS) + SET(_cmake_configure_command + ${CMAKE_COMMAND} + ) + LIST(APPEND _cmake_configure_command "-DCMAKE_INSTALL_PREFIX=${_install_dir}") + LIST(APPEND _cmake_configure_command "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}") + LIST(APPEND _cmake_configure_command "-DCMAKE_INSTALL_PREFIX=${_install_dir}") + LIST(APPEND _cmake_configure_command "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") + LIST(APPEND _cmake_configure_command "-Denable_parallel_mark=ON") + LIST(APPEND _cmake_configure_command "-Denable_cplusplus=ON") + LIST(APPEND _cmake_configure_command "-DCMAKE_USE_WIN32_THREADS_INIT=1") + LIST(APPEND _cmake_configure_command "${RV_DEPS_BASE_DIR}/${_target}/src") + EXTERNALPROJECT_ADD( + ${_target} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + INSTALL_DIR ${_install_dir} + URL ${_download_url} + URL_MD5 ${_download_hash} + CONFIGURE_COMMAND ${_cmake_configure_command} + BUILD_COMMAND ${_make_command} -j${_cpu_count} -v + INSTALL_COMMAND ${CMAKE_COMMAND} -E copy_directory ${RV_DEPS_BASE_DIR}/${_target}/src/include ${_include_dir}/gc + COMMAND ${CMAKE_COMMAND} -E copy ${RV_DEPS_BASE_DIR}/${_target}/src/${_gc_lib_name} ${_gc_lib} + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_gc_byproducts} + USES_TERMINAL_BUILD TRUE + ) + ADD_LIBRARY(BDWGC::Gc STATIC IMPORTED GLOBAL) +ELSE() + SET(_configure_args + "--enable-cplusplus" + ) + LIST(APPEND _configure_args "--prefix=${_install_dir}") + IF(RV_TARGET_MACOS) + LIST(APPEND _configure_args MACOSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}) + ENDIF() + EXTERNALPROJECT_ADD( + ${_target} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + INSTALL_DIR ${_install_dir} + URL ${_download_url} + URL_MD5 ${_download_hash} + CONFIGURE_COMMAND ${_autogen_command} && ${_configure_command} ${_configure_args} + BUILD_COMMAND ${_make_command} -j${_cpu_count} -v + INSTALL_COMMAND ${_make_command} install + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir} ${CMAKE_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/lib ${RV_STAGE_LIB_DIR} + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_gc_byproducts} + USES_TERMINAL_BUILD TRUE + ) + ADD_LIBRARY(BDWGC::Gc SHARED IMPORTED GLOBAL) +ENDIF() + +ADD_DEPENDENCIES(BDWGC::Gc ${_target}) +SET_PROPERTY( + TARGET BDWGC::Gc + PROPERTY IMPORTED_LOCATION ${_gc_lib} +) +SET_PROPERTY( + TARGET BDWGC::Gc + PROPERTY IMPORTED_SONAME ${_gc_lib_name} +) +TARGET_INCLUDE_DIRECTORIES( + BDWGC::Gc + INTERFACE ${_include_dir} +) +LIST(APPEND RV_DEPS_LIST BDWGC::Gc) + +IF(NOT RV_TARGET_WINDOWS) + ADD_LIBRARY(BDWGC::Cord SHARED IMPORTED GLOBAL) + ADD_DEPENDENCIES(BDWGC::Cord ${_target}) + SET_PROPERTY( + TARGET BDWGC::Cord + PROPERTY IMPORTED_LOCATION ${_cord_lib} + ) + SET_PROPERTY( + TARGET BDWGC::Cord + PROPERTY IMPORTED_SONAME ${_cord_lib_name} + ) + TARGET_INCLUDE_DIRECTORIES( + BDWGC::Cord + INTERFACE ${_include_dir} + ) + LIST(APPEND RV_DEPS_LIST BDWGC::Cord) +ENDIF() + +ADD_CUSTOM_COMMAND( + COMMENT "Installing ${_target}'s libs into ${RV_STAGE_LIB_DIR}" + OUTPUT ${_gc_stage_outputs} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + DEPENDS ${_target} +) +ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${_gc_stage_outputs} +) + +ADD_DEPENDENCIES(dependencies ${_target}-stage-target) + +SET(RV_DEPS_GC_VERSION + ${_version} + CACHE INTERNAL "" +) diff --git a/cmake/dependencies/glew.cmake b/cmake/dependencies/glew.cmake new file mode 100644 index 000000000..faab0ef77 --- /dev/null +++ b/cmake/dependencies/glew.cmake @@ -0,0 +1,115 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(ProcessorCount) # require CMake 3.15+ +PROCESSORCOUNT(_cpu_count) + +SET(_target + "RV_DEPS_GLEW" +) + +SET(_version + "2.2.0" +) + +SET(_download_url + "https://github.com/nigels-com/glew/archive/refs/tags/glew-${_version}.zip" +) + +SET(_download_hash + f150f61074d049ff0423b09b18cd1ef6 +) + +SET(_install_dir + ${RV_DEPS_BASE_DIR}/${_target}/install +) + +IF(RV_TARGET_LINUX) + SET(_lib_dir + ${_install_dir}/lib64 + ) +ELSE() + SET(_lib_dir + ${_install_dir}/lib + ) +ENDIF() + +IF(RV_TARGET_DARWIN) + SET(_glew_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}GLEW.2.2.0${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ELSE() + SET(_glew_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}GLEW${CMAKE_SHARED_LIBRARY_SUFFIX}.2.2.0 + ) +ENDIF() +SET(_glew_lib + ${_lib_dir}/${_glew_lib_name} +) + +SET(_make_command + make +) + +IF(${RV_OSX_EMULATION}) + SET(_darwin_x86_64 + "arch" "${RV_OSX_EMULATION_ARCH}" + ) + + SET(_make_command + ${_darwin_x86_64} ${_make_command} + ) +ENDIF() + +EXTERNALPROJECT_ADD( + ${_target} + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + INSTALL_DIR ${_install_dir} + URL ${_download_url} + URL_MD5 ${_download_hash} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + CONFIGURE_COMMAND cd auto && ${_make_command} + BUILD_COMMAND ${_make_command} -j${_cpu_count} GLEW_DEST=${_install_dir} + INSTALL_COMMAND ${_make_command} install GLEW_DEST=${_install_dir} + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_glew_lib} + USES_TERMINAL_BUILD TRUE +) + +ADD_LIBRARY(GLEW::GLEW STATIC IMPORTED GLOBAL) +ADD_DEPENDENCIES(GLEW::GLEW ${_target}) +SET_PROPERTY( + TARGET GLEW::GLEW + PROPERTY IMPORTED_LOCATION ${_glew_lib} +) + +FILE(MAKE_DIRECTORY ${_include_dir}) +TARGET_INCLUDE_DIRECTORIES( + GLEW::GLEW + INTERFACE ${_include_dir} +) +LIST(APPEND RV_DEPS_LIST GLEW::GLEW) + +ADD_CUSTOM_COMMAND( + COMMENT "Installing ${_target}'s libs into ${RV_STAGE_LIB_DIR}" + OUTPUT ${RV_STAGE_LIB_DIR}/${_glew_lib_name} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + DEPENDS ${_target} +) + +ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_LIB_DIR}/${_glew_lib_name} +) + +ADD_DEPENDENCIES(dependencies ${_target}-stage-target) + +SET(RV_DEPS_GLEW_VERSION + ${_version} + CACHE INTERNAL "" FORCE +) diff --git a/cmake/dependencies/imath.cmake b/cmake/dependencies/imath.cmake new file mode 100644 index 000000000..d2e4a6e5d --- /dev/null +++ b/cmake/dependencies/imath.cmake @@ -0,0 +1,179 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(ProcessorCount) # require CMake 3.15+ +PROCESSORCOUNT(_cpu_count) + +SET(_target + "RV_DEPS_IMATH" +) + +SET(_version + "3.1.5" +) + +SET(_download_url + "https://github.com/AcademySoftwareFoundation/Imath/archive/refs/tags/v${_version}.zip" +) + +SET(_download_hash + "921ac54505ab076a95b33a16b61956f4" +) + +SET(_install_dir + ${RV_DEPS_BASE_DIR}/${_target}/install +) +SET(_include_dir + ${_install_dir}/include/Imath +) + +SET(_make_command + make +) +SET(_configure_command + cmake +) + +IF(${RV_OSX_EMULATION}) + SET(_darwin_x86_64 + "arch" "${RV_OSX_EMULATION_ARCH}" + ) + + SET(_make_command + ${_darwin_x86_64} ${_make_command} + ) + SET(_configure_command + ${_darwin_x86_64} ${_configure_command} + ) +ENDIF() + +IF(RV_TARGET_WINDOWS) + # MSYS2/CMake defaults to Ninja + SET(_make_command + ninja + ) +ENDIF() + +IF(RV_TARGET_DARWIN) + SET(_libname + ${CMAKE_SHARED_LIBRARY_PREFIX}Imath-3_1${RV_DEBUG_POSTFIX}.29.4.0${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ELSEIF(RV_TARGET_LINUX) + SET(_libname + ${CMAKE_SHARED_LIBRARY_PREFIX}Imath-3_1${RV_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}.29.4.0 + ) +ELSEIF(RV_TARGET_WINDOWS) + SET(_libname + ${CMAKE_SHARED_LIBRARY_PREFIX}Imath-3_1${RV_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ENDIF() + +IF(RV_TARGET_LINUX) + SET(_lib_dir + ${_install_dir}/lib64 + ) +ELSE() + SET(_lib_dir + ${_install_dir}/lib + ) +ENDIF() + +SET(RV_DEPS_IMATH_CMAKE_DIR + ${_lib_dir}/cmake/Imath + CACHE STRING "Path to Imath CMake files ${_target}" +) + +SET(RV_DEPS_IMATH_CMAKE_DIR + ${_lib_dir}/cmake/Imath +) + +SET(_libpath + ${_lib_dir}/${_libname} +) + +LIST(APPEND _imath_byproducts ${_libpath}) + +IF(RV_TARGET_WINDOWS) + SET(_implibpath + ${_install_dir}/lib/${CMAKE_IMPORT_LIBRARY_PREFIX}Imath-3_1${RV_DEBUG_POSTFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX} + ) + LIST(APPEND _imath_byproducts ${_implibpath}) +ENDIF() + +EXTERNALPROJECT_ADD( + ${_target} + URL ${_download_url} + URL_MD5 ${_download_hash} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + INSTALL_DIR ${_install_dir} + CONFIGURE_COMMAND ${CMAKE_COMMAND} -DCMAKE_INSTALL_PREFIX=${_install_dir} -DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} ${RV_DEPS_BASE_DIR}/${_target}/src + BUILD_COMMAND ${_make_command} -j${_cpu_count} -v + INSTALL_COMMAND ${_make_command} install + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_imath_byproducts} + USES_TERMINAL_BUILD TRUE +) + +FILE(MAKE_DIRECTORY "${_include_dir}") + +IF(RV_TARGET_WINDOWS) + ADD_CUSTOM_COMMAND( + TARGET ${_target} + POST_BUILD + COMMENT "Installing ${_target}'s libs and bin into ${RV_STAGE_LIB_DIR} and ${RV_STAGE_BIN_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/lib ${RV_STAGE_LIB_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/bin ${RV_STAGE_BIN_DIR} + ) + ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_BIN_DIR}/${_libname} + ) +ELSE() + ADD_CUSTOM_COMMAND( + COMMENT "Installing ${_target}'s libs into ${RV_STAGE_LIB_DIR}" + OUTPUT ${RV_STAGE_LIB_DIR}/${_libname} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + DEPENDS ${_target} + ) + ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_LIB_DIR}/${_libname} + ) +ENDIF() + +ADD_DEPENDENCIES(dependencies ${_target}-stage-target) + +ADD_LIBRARY(Imath::Imath SHARED IMPORTED GLOBAL) +ADD_DEPENDENCIES(Imath::Imath ${_target}) +SET_PROPERTY( + TARGET Imath::Imath + PROPERTY IMPORTED_LOCATION ${_libpath} +) +SET_PROPERTY( + TARGET Imath::Imath + PROPERTY IMPORTED_SONAME ${_libname} +) +IF(RV_TARGET_WINDOWS) + SET_PROPERTY( + TARGET Imath::Imath + PROPERTY IMPORTED_IMPLIB ${_implibpath} + ) +ENDIF() +TARGET_INCLUDE_DIRECTORIES( + Imath::Imath + INTERFACE ${_include_dir} +) +LIST(APPEND RV_DEPS_LIST Imath::Imath) + +SET(RV_DEPS_IMATH_VERSION + ${_version} + CACHE INTERNAL "" FORCE +) diff --git a/cmake/dependencies/openexr.cmake b/cmake/dependencies/openexr.cmake new file mode 100644 index 000000000..47a82b1e8 --- /dev/null +++ b/cmake/dependencies/openexr.cmake @@ -0,0 +1,271 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(ProcessorCount) # require CMake 3.15+ +PROCESSORCOUNT(_cpu_count) + +SET(_target + "RV_DEPS_OPENEXR" +) + +SET(_version + "3.1.5" +) +SET(_download_url + "https://github.com/AcademySoftwareFoundation/openexr/archive/refs/tags/v${_version}.zip" +) + +SET(_download_hash + "a211c5c9f0796ad5c895fe0d2ce6a3f8" +) + +SET(_install_dir + ${RV_DEPS_BASE_DIR}/${_target}/install +) + +IF(RV_TARGET_LINUX) + SET(_lib_dir + ${_install_dir}/lib64 + ) +ELSE() + SET(_lib_dir + ${_install_dir}/lib + ) +ENDIF() + +SET(_make_command + make +) +IF(${RV_OSX_EMULATION}) + SET(_darwin_x86_64 + "arch" "${RV_OSX_EMULATION_ARCH}" + ) + SET(_make_command + ${_darwin_x86_64} ${_make_command} + ) +ENDIF() +IF(RV_TARGET_WINDOWS) + # MSYS2/CMake defaults to Ninja + SET(_make_command + ninja + ) +ENDIF() + +IF(RV_TARGET_WINDOWS) + SET(_openexr_name + ${CMAKE_SHARED_LIBRARY_PREFIX}OpenEXR-3_1${RV_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ELSE() + SET(_openexr_name + ${CMAKE_SHARED_LIBRARY_PREFIX}OpenEXR${RV_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ENDIF() +SET(_openexr_lib + ${_lib_dir}/${_openexr_name} +) + +IF(RV_TARGET_DARWIN) + SET(_openexrcore_name + ${CMAKE_SHARED_LIBRARY_PREFIX}OpenEXRCore-3_1${RV_DEBUG_POSTFIX}.30.5.1${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ELSEIF(RV_TARGET_LINUX) + SET(_openexrcore_name + ${CMAKE_SHARED_LIBRARY_PREFIX}OpenEXRCore-3_1${RV_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}.30.5.1 + ) +ELSEIF(RV_TARGET_WINDOWS) + SET(_openexrcore_name + ${CMAKE_SHARED_LIBRARY_PREFIX}OpenEXRCore-3_1${RV_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ENDIF() + +SET(_openexrcore_lib + ${_lib_dir}/${_openexrcore_name} +) + +IF(RV_TARGET_DARWIN) + SET(_ilmthread_name + ${CMAKE_SHARED_LIBRARY_PREFIX}IlmThread-3_1${RV_DEBUG_POSTFIX}.30.5.1${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ELSEIF(RV_TARGET_LINUX) + SET(_ilmthread_name + ${CMAKE_SHARED_LIBRARY_PREFIX}IlmThread-3_1${RV_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}.30.5.1 + ) +ELSEIF(RV_TARGET_WINDOWS) + SET(_ilmthread_name + ${CMAKE_SHARED_LIBRARY_PREFIX}IlmThread-3_1${RV_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ENDIF() + +SET(_ilmthread_lib + ${_lib_dir}/${_ilmthread_name} +) + +IF(RV_TARGET_DARWIN) + SET(_iex_name + ${CMAKE_SHARED_LIBRARY_PREFIX}Iex-3_1${RV_DEBUG_POSTFIX}.30.5.1${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ELSEIF(RV_TARGET_LINUX) + SET(_iex_name + ${CMAKE_SHARED_LIBRARY_PREFIX}Iex-3_1${RV_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}.30.5.1 + ) +ELSEIF(RV_TARGET_WINDOWS) + SET(_iex_name + ${CMAKE_SHARED_LIBRARY_PREFIX}Iex-3_1${RV_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX} + ) +ENDIF() + +SET(_iex_lib + ${_lib_dir}/${_iex_name} +) + +LIST(APPEND _openexr_byproducts ${_openexr_lib} ${_ilmthread_lib} ${_iex_lib}) + +IF(RV_TARGET_WINDOWS) + SET(_openexr_implib + ${_install_dir}/lib/${CMAKE_IMPORT_LIBRARY_PREFIX}OpenEXR-3_1${RV_DEBUG_POSTFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX} + ) + SET(_ilmthread_implib + ${_install_dir}/lib/${CMAKE_IMPORT_LIBRARY_PREFIX}IlmThread-3_1${RV_DEBUG_POSTFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX} + ) + SET(_iex_implib + ${_install_dir}/lib/${CMAKE_IMPORT_LIBRARY_PREFIX}Iex-3_1${RV_DEBUG_POSTFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX} + ) + + LIST(APPEND _openexr_byproducts ${_openexr_implib} ${_ilmthread_implib} ${_iex_implib}) +ENDIF() + +SET(_cmake_configure_command + ${CMAKE_COMMAND} +) +LIST(APPEND _cmake_configure_command "-DCMAKE_INSTALL_PREFIX=${_install_dir}") +LIST(APPEND _cmake_configure_command "-DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES}") +LIST(APPEND _cmake_configure_command "-DCMAKE_INSTALL_PREFIX=${_install_dir}") +LIST(APPEND _cmake_configure_command "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}") +LIST(APPEND _cmake_configure_command "-DCMAKE_PREFIX_PATH=${RV_DEPS_IMATH_CMAKE_DIR}") +LIST(APPEND _cmake_configure_command "-DBUILD_TESTING=OFF") +LIST(APPEND _cmake_configure_command "${RV_DEPS_BASE_DIR}/${_target}/src") +IF(RV_TARGET_WINDOWS) + GET_TARGET_PROPERTY(_zlib_implibpath ZLIB::ZLIB IMPORTED_IMPLIB) + LIST(APPEND _cmake_configure_command "-DZLIB_INCLUDE_DIR=${RV_DEPS_ZLIB_INCLUDE_DIR}") + LIST(APPEND _cmake_configure_command "-DZLIB_LIBRARY=${_zlib_implibpath}") +ENDIF() + +EXTERNALPROJECT_ADD( + ${_target} + URL ${_download_url} + URL_MD5 ${_download_hash} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + DEPENDS Imath::Imath ZLIB::ZLIB + INSTALL_DIR ${_install_dir} + CONFIGURE_COMMAND ${_cmake_configure_command} + BUILD_COMMAND ${_make_command} -j${_cpu_count} -v + INSTALL_COMMAND ${_make_command} install + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_openexr_byproducts} + USES_TERMINAL_BUILD TRUE +) + +SET(_include_dir + ${_install_dir}/include/OpenEXR +) +FILE(MAKE_DIRECTORY ${_include_dir}) + +ADD_DEPENDENCIES(dependencies ${_target}-stage-target) + +IF(RV_TARGET_WINDOWS) + ADD_CUSTOM_COMMAND( + TARGET ${_target} + POST_BUILD + COMMENT "Installing ${_target}'s libs and bin into ${RV_STAGE_LIB_DIR} and ${RV_STAGE_BIN_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/lib ${RV_STAGE_LIB_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/bin ${RV_STAGE_BIN_DIR} + ) + ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_BIN_DIR}/${_openexrcore_name} ${RV_STAGE_BIN_DIR}/${_ilmthread_name} ${RV_STAGE_BIN_DIR}/${_iex_name} + ) +ELSE() + ADD_CUSTOM_COMMAND( + COMMENT "Installing ${_target}'s libs into ${RV_STAGE_LIB_DIR}" + OUTPUT ${RV_STAGE_LIB_DIR}/${_openexrcore_name} ${RV_STAGE_LIB_DIR}/${_ilmthread_name} ${RV_STAGE_LIB_DIR}/${_iex_name} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + DEPENDS ${_target} + ) + ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_LIB_DIR}/${_openexrcore_name} ${RV_STAGE_LIB_DIR}/${_ilmthread_name} ${RV_STAGE_LIB_DIR}/${_iex_name} + ) +ENDIF() + +ADD_LIBRARY(OpenEXR::IlmThread SHARED IMPORTED GLOBAL) +SET_PROPERTY( + TARGET OpenEXR::IlmThread + PROPERTY IMPORTED_LOCATION ${_ilmthread_lib} +) +SET_PROPERTY( + TARGET OpenEXR::IlmThread + PROPERTY IMPORTED_SONAME ${_ilmthread_name} +) +IF(RV_TARGET_WINDOWS) + SET_PROPERTY( + TARGET OpenEXR::IlmThread + PROPERTY IMPORTED_IMPLIB ${_ilmthread_implib} + ) +ENDIF() +LIST(APPEND RV_DEPS_LIST OpenEXR::IlmThread) + +ADD_LIBRARY(OpenEXR::Iex SHARED IMPORTED GLOBAL) +SET_PROPERTY( + TARGET OpenEXR::Iex + PROPERTY IMPORTED_LOCATION ${_iex_lib} +) +SET_PROPERTY( + TARGET OpenEXR::Iex + PROPERTY IMPORTED_SONAME ${_iex_name} +) +IF(RV_TARGET_WINDOWS) + SET_PROPERTY( + TARGET OpenEXR::Iex + PROPERTY IMPORTED_IMPLIB ${_iex_implib} + ) +ENDIF() +LIST(APPEND RV_DEPS_LIST OpenEXR::Iex) + +ADD_LIBRARY(OpenEXR::OpenEXR SHARED IMPORTED GLOBAL) +SET_PROPERTY( + TARGET OpenEXR::OpenEXR + PROPERTY IMPORTED_LOCATION ${_openexr_lib} +) +SET_PROPERTY( + TARGET OpenEXR::OpenEXR + PROPERTY IMPORTED_SONAME ${_openexr_name} +) +IF(RV_TARGET_WINDOWS) + SET_PROPERTY( + TARGET OpenEXR::OpenEXR + PROPERTY IMPORTED_IMPLIB ${_openexr_implib} + ) +ENDIF() +TARGET_INCLUDE_DIRECTORIES( + OpenEXR::OpenEXR + INTERFACE ${_include_dir} +) +TARGET_LINK_LIBRARIES( + OpenEXR::OpenEXR + INTERFACE Imath::Imath OpenEXR::Iex OpenEXR::IlmThread +) +LIST(APPEND RV_DEPS_LIST OpenEXR::OpenEXR) + +ADD_DEPENDENCIES(OpenEXR::OpenEXR ${_target}) + +SET(RV_DEPS_OPENEXR_VERSION + ${_version} + CACHE INTERNAL "" FORCE +) diff --git a/cmake/dependencies/openssl.cmake b/cmake/dependencies/openssl.cmake new file mode 100644 index 000000000..7c234b4ad --- /dev/null +++ b/cmake/dependencies/openssl.cmake @@ -0,0 +1,168 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +SET(RV_DEPS_WIN_PERL_ROOT + "" + CACHE STRING "Path to Windows perl root" +) + +SET(_target + "RV_DEPS_OPENSSL" +) + +SET(_version + "1.1.1p" +) + +IF(RV_TARGET_WINDOWS + AND (NOT RV_DEPS_WIN_PERL_ROOT + OR RV_DEPS_WIN_PERL_ROOT STREQUAL "") +) + MESSAGE( + FATAL_ERROR + "Unable to build without a RV_DEPS_WIN_PERL_ROOT. OpenSSL requires a Windows native perl interpreter to build (it recommends https://strawberryperl.com/). Example -DRV_DEPS_WIN_PERL_ROOT=c:/Strawberry/perl/bin" + ) +ENDIF() + +SET(_install_dir + ${RV_DEPS_BASE_DIR}/${_target}/install +) +SET(_source_dir + ${RV_DEPS_BASE_DIR}/${_target}/src +) +SET(_build_dir + ${RV_DEPS_BASE_DIR}/${_target}/build +) +SET(RV_DEPS_OPENSSL_LIB_DIR + ${_install_dir}/lib +) + +SET(_download_url + "https://www.openssl.org/source/openssl-${_version}.tar.gz" +) +SET(_download_hash + "3d610fed4f44ce4d4b42849a368d2071" +) + +SET(_make_command_script + "${CMAKE_SOURCE_DIR}/src/build/make_openssl.py" +) +SET(_make_command + python3 "${_make_command_script}" +) + +LIST(APPEND _make_command "--source-dir") +LIST(APPEND _make_command ${_source_dir}) +LIST(APPEND _make_command "--output-dir") +LIST(APPEND _make_command ${_install_dir}) +IF(RV_TARGET_WINDOWS) + LIST(APPEND _make_command "--perlroot") + LIST(APPEND _make_command ${RV_DEPS_WIN_PERL_ROOT}) +ENDIF() + +IF(${RV_OSX_EMULATION}) + LIST(APPEND _make_command --arch=${RV_OSX_EMULATION_ARCH}) +ENDIF() + +SET(_crypto_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}crypto.1.1${CMAKE_SHARED_LIBRARY_SUFFIX} +) +SET(_crypto_lib + ${RV_DEPS_OPENSSL_LIB_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}crypto.1.1${CMAKE_SHARED_LIBRARY_SUFFIX} +) +SET(_ssl_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}ssl.1.1${CMAKE_SHARED_LIBRARY_SUFFIX} +) +SET(_ssl_lib + ${RV_DEPS_OPENSSL_LIB_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}ssl.1.1${CMAKE_SHARED_LIBRARY_SUFFIX} +) + +EXTERNALPROJECT_ADD( + ${_target} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${_source_dir} + INSTALL_DIR ${_install_dir} + URL ${_download_url} + URL_MD5 ${_download_hash} + CONFIGURE_COMMAND ${_make_command} --configure + BUILD_COMMAND ${_make_command} --build + INSTALL_COMMAND ${_make_command} --install + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_crypto_lib} ${_ssl_lib} + USES_TERMINAL_BUILD TRUE +) + +SET(_include_dir + ${_install_dir}/include +) +FILE(MAKE_DIRECTORY ${_include_dir}) + +ADD_LIBRARY(OpenSSL::Crypto SHARED IMPORTED GLOBAL) +ADD_DEPENDENCIES(OpenSSL::Crypto ${_target}) +SET_PROPERTY( + TARGET OpenSSL::Crypto + PROPERTY IMPORTED_LOCATION ${_crypto_lib} +) +SET_PROPERTY( + TARGET OpenSSL::Crypto + PROPERTY IMPORTED_SONAME ${_crypto_lib_name} +) +TARGET_INCLUDE_DIRECTORIES( + OpenSSL::Crypto + INTERFACE ${_include_dir} +) +LIST(APPEND RV_DEPS_LIST OpenSSL::Crypto) + +ADD_LIBRARY(OpenSSL::SSL SHARED IMPORTED GLOBAL) +ADD_DEPENDENCIES(OpenSSL::SSL ${_target}) +SET_PROPERTY( + TARGET OpenSSL::SSL + PROPERTY IMPORTED_LOCATION ${_ssl_lib} +) +SET_PROPERTY( + TARGET OpenSSL::SSL + PROPERTY IMPORTED_SONAME ${_ssl_lib_name} +) +TARGET_INCLUDE_DIRECTORIES( + OpenSSL::SSL + INTERFACE ${_include_dir} +) +LIST(APPEND RV_DEPS_LIST OpenSSL::SSL) + +IF(RV_TARGET_WINDOWS) + ADD_CUSTOM_COMMAND( + TARGET ${_target} + POST_BUILD + COMMENT "Installing ${_target}'s libs and bin into ${RV_STAGE_LIB_DIR} and ${RV_STAGE_BIN_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/lib ${RV_STAGE_LIB_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/bin ${RV_STAGE_BIN_DIR} + ) + ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_BIN_DIR}/${_crypto_lib_name} ${RV_STAGE_BIN_DIR}/${_ssl_lib_name} + ) +ELSE() + ADD_CUSTOM_COMMAND( + COMMENT "Installing ${_target}'s libs into ${RV_STAGE_LIB_DIR}" + OUTPUT ${RV_STAGE_LIB_DIR}/${_crypto_lib_name} ${RV_STAGE_LIB_DIR}/${_ssl_lib_name} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${RV_DEPS_OPENSSL_LIB_DIR} ${RV_STAGE_LIB_DIR} + DEPENDS ${_target} + ) + ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_LIB_DIR}/${_crypto_lib_name} ${RV_STAGE_LIB_DIR}/${_ssl_lib_name} + ) +ENDIF() + +ADD_DEPENDENCIES(dependencies ${_target}-stage-target) + +SET(RV_DEPS_OPENSSL_VERSION + ${_version} + CACHE INTERNAL "" FORCE +) diff --git a/cmake/dependencies/python3.cmake b/cmake/dependencies/python3.cmake new file mode 100644 index 000000000..12cca7ef2 --- /dev/null +++ b/cmake/dependencies/python3.cmake @@ -0,0 +1,303 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +SET(_python3_target + "RV_DEPS_PYTHON3" +) + +SET(_opentimelineio_target + "RV_DEPS_OPENTIMELINEIO" +) + +SET(_pyside2_target + "RV_DEPS_PYSIDE2" +) + +SET(_python3_version + "3.9.15" +) + +SET(_opentimelineio_version + "0.15" +) + +SET(_pyside2_version + "5.15.2.1" +) + +SET(_python3_download_url + "https://github.com/python/cpython/archive/refs/tags/v${_python3_version}.zip" +) +SET(_python3_download_hash + "310405aa7d54dcfb8890930667ac3cdb" +) + +SET(_opentimelineio_download_url + "https://github.com/AcademySoftwareFoundation/OpenTimelineIO" +) +SET(_opentimelineio_git_tag + "v${_opentimelineio_version}" +) + +SET(_pyside2_git_repo_url + "git://code.qt.io/pyside/pyside-setup.git" +) +SET(_pyside2_git_tag + "v${_pyside2_version}" +) + +SET(_install_dir + ${RV_DEPS_BASE_DIR}/${_python3_target}/install +) +SET(_source_dir + ${RV_DEPS_BASE_DIR}/${_python3_target}/src +) +SET(_build_dir + ${RV_DEPS_BASE_DIR}/${_python3_target}/build +) + +STRING(REPLACE "." ";" _version_list ${_python3_version}) +LIST(GET _version_list 0 _python3_version_major) +LIST(GET _version_list 1 _python3_version_minor) + +IF(RV_TARGET_WINDOWS) + + FETCHCONTENT_DECLARE( + ${_opentimelineio_target} + GIT_REPOSITORY ${_opentimelineio_download_url} + GIT_TAG ${_opentimelineio_git_tag} + SOURCE_SUBDIR "src" # Avoids the top level CMakeLists.txt + ) + + FETCHCONTENT_MAKEAVAILABLE(${_opentimelineio_target}) + +ENDIF() + +FETCHCONTENT_DECLARE( + ${_pyside2_target} + GIT_REPOSITORY ${_pyside2_git_repo_url} + GIT_TAG ${_pyside2_git_tag} + SOURCE_SUBDIR "sources" # Avoids the top level CMakeLists.txt +) + +FETCHCONTENT_MAKEAVAILABLE(${_pyside2_target}) + +SET(_python3_make_command_script + "${CMAKE_SOURCE_DIR}/src/build/make_python.py" +) +SET(_python3_make_command + python3 "${_python3_make_command_script}" +) +LIST(APPEND _python3_make_command "--variant") +LIST(APPEND _python3_make_command ${CMAKE_BUILD_TYPE}) +LIST(APPEND _python3_make_command "--source-dir") +LIST(APPEND _python3_make_command ${_source_dir}) +LIST(APPEND _python3_make_command "--output-dir") +LIST(APPEND _python3_make_command ${_install_dir}) +LIST(APPEND _python3_make_command "--temp-dir") +LIST(APPEND _python3_make_command ${_build_dir}) +LIST(APPEND _python3_make_command "--openssl-dir") +LIST(APPEND _python3_make_command ${RV_DEPS_BASE_DIR}/${_target}/install) +IF(RV_TARGET_WINDOWS) + LIST(APPEND _python3_make_command "--opentimelineio-source-dir") + LIST(APPEND _python3_make_command ${rv_deps_opentimelineio_SOURCE_DIR}) +ENDIF() + +IF(${RV_OSX_EMULATION}) + LIST(APPEND _python3_make_command --arch=${RV_OSX_EMULATION_ARCH}) +ENDIF() + +SET(_pyside2_make_command_script + "${CMAKE_SOURCE_DIR}/src/build/make_pyside.py" +) +SET(_pyside2_make_command + python3 "${_pyside2_make_command_script}" +) +LIST(APPEND _pyside2_make_command "--variant") +LIST(APPEND _pyside2_make_command ${CMAKE_BUILD_TYPE}) +LIST(APPEND _pyside2_make_command "--source-dir") +LIST(APPEND _pyside2_make_command ${rv_deps_pyside2_SOURCE_DIR}) +LIST(APPEND _pyside2_make_command "--output-dir") +LIST(APPEND _pyside2_make_command ${_install_dir}) +LIST(APPEND _pyside2_make_command "--temp-dir") +LIST(APPEND _pyside2_make_command ${_build_dir}) +LIST(APPEND _pyside2_make_command "--openssl-dir") +LIST(APPEND _pyside2_make_command ${RV_DEPS_BASE_DIR}/${_target}/install) +LIST(APPEND _pyside2_make_command "--python-dir") +LIST(APPEND _pyside2_make_command ${_install_dir}) +LIST(APPEND _pyside2_make_command "--qt-dir") +LIST(APPEND _pyside2_make_command ${RV_DEPS_QT5_LOCATION}) + +IF(RV_TARGET_WINDOWS) + IF(CMAKE_BUILD_TYPE MATCHES "^Debug$") + SET(PYTHON3_EXTRA_WIN_LIBRARY_SUFFIX_IF_DEBUG + "_d" + ) + ELSE() + SET(PYTHON3_EXTRA_WIN_LIBRARY_SUFFIX_IF_DEBUG + "" + ) + ENDIF() + SET(_python_name + python${_python3_version_major}${_python3_version_minor}${PYTHON3_EXTRA_WIN_LIBRARY_SUFFIX_IF_DEBUG} + ) + SET(_include_dir + ${_install_dir}/include + ) + SET(_bin_dir + ${_install_dir}/bin + ) + SET(_lib_dir + ${_install_dir}/libs + ) + SET(_python3_lib_name + ${_python_name}${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + SET(_python3_lib + ${_bin_dir}/${_python3_lib_name} + ) + SET(_python3_implib + ${_lib_dir}/${_python_name}${CMAKE_IMPORT_LIBRARY_SUFFIX} + ) + SET(_python3_executable + ${_bin_dir}/python${PYTHON3_EXTRA_WIN_LIBRARY_SUFFIX_IF_DEBUG}.exe + ) +ELSE() # Not WINDOWS + SET(_python_name + python${_python3_version_major}.${_python3_version_minor} + ) + SET(_include_dir + ${_install_dir}/include/${_python_name} + ) + SET(_lib_dir + ${_install_dir}/lib + ) + SET(_python3_lib_name + ${CMAKE_SHARED_LIBRARY_PREFIX}${_python_name}${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + SET(_python3_lib + ${_lib_dir}/${_python3_lib_name} + ) + SET(_python3_executable + ${_install_dir}/bin/python3 + ) +ENDIF() + +SET(_requirements_file + "${CMAKE_SOURCE_DIR}/src/build/requirements.txt" +) +SET(_requirements_install_command + "${_python3_executable}" -m pip install --upgrade -r "${_requirements_file}" +) + +EXTERNALPROJECT_ADD( + ${_python3_target} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${_source_dir} + INSTALL_DIR ${_install_dir} + URL ${_python3_download_url} + URL_MD5 ${_python3_download_hash} + DEPENDS OpenSSL::Crypto OpenSSL::SSL + CONFIGURE_COMMAND ${_python3_make_command} --configure + BUILD_COMMAND ${_python3_make_command} --build + INSTALL_COMMAND ${_python3_make_command} --install + BUILD_BYPRODUCTS ${_python3_executable} ${_python3_lib} ${_python3_implib} + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + USES_TERMINAL_BUILD TRUE +) + +SET(${_python3_target}-requirements-flag + ${_install_dir}/${_python3_target}-requirements-flag +) + +ADD_CUSTOM_COMMAND( + COMMENT "Installing requirements from ${_requirements_file}" + OUTPUT ${${_python3_target}-requirements-flag} + COMMAND ${_requirements_install_command} + COMMAND cmake -E touch ${${_python3_target}-requirements-flag} + DEPENDS ${_python3_target} ${_requirements_file} +) + +SET(${_pyside2_target}-build-flag + ${_install_dir}/${_pyside2_target}-build-flag +) + +ADD_CUSTOM_COMMAND( + COMMENT "Building PySide2 using ${_pyside2_make_command_script}" + OUTPUT ${${_pyside2_target}-build-flag} + # First PySide build script on Windows which doesn't respect '--debug' option + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/src/build/patch_PySide2/windows_desktop.py + ${rv_deps_pyside2_SOURCE_DIR}/build_scripts/platforms/windows_desktop.py + COMMAND ${_pyside2_make_command} --prepare --build + COMMAND cmake -E touch ${${_pyside2_target}-build-flag} + DEPENDS ${_python3_target} ${_pyside2_make_command_script} ${${_python3_target}-requirements-flag} + USES_TERMINAL +) + +IF(RV_TARGET_WINDOWS) + ADD_CUSTOM_COMMAND( + COMMENT "Installing ${_python3_target}'s include and libs into ${RV_STAGE_LIB_DIR}" + OUTPUT ${RV_STAGE_BIN_DIR}/${_python3_lib_name} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/lib ${RV_STAGE_LIB_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/include ${RV_STAGE_INCLUDE_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/bin ${RV_STAGE_BIN_DIR} + DEPENDS ${_python3_target} ${${_pyside2_target}-build-flag} ${${_python3_target}-requirements-flag} + ) + ADD_CUSTOM_TARGET( + ${_python3_target}-stage-target ALL + DEPENDS ${RV_STAGE_BIN_DIR}/${_python3_lib_name} + ) +ELSE() + ADD_CUSTOM_COMMAND( + COMMENT "Installing ${_python3_target}'s include and libs into ${RV_STAGE_LIB_DIR}" + OUTPUT ${RV_STAGE_LIB_DIR}/${_python3_lib_name} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/lib ${RV_STAGE_LIB_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/include ${RV_STAGE_INCLUDE_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_install_dir}/bin ${RV_STAGE_BIN_DIR} + DEPENDS ${_python3_target} ${${_pyside2_target}-build-flag} ${${_python3_target}-requirements-flag} + ) + ADD_CUSTOM_TARGET( + ${_python3_target}-stage-target ALL + DEPENDS ${RV_STAGE_LIB_DIR}/${_python3_lib_name} + ) +ENDIF() + +ADD_LIBRARY(Python::Python SHARED IMPORTED GLOBAL) +ADD_DEPENDENCIES(Python::Python ${_python3_target}) +SET_PROPERTY( + TARGET Python::Python + PROPERTY IMPORTED_LOCATION ${_python3_lib} +) +SET_PROPERTY( + TARGET Python::Python + PROPERTY IMPORTED_SONAME ${_python3_lib_name} +) +IF(RV_TARGET_WINDOWS) + SET_PROPERTY( + TARGET Python::Python + PROPERTY IMPORTED_IMPLIB ${_python3_implib} + ) +ENDIF() +FILE(MAKE_DIRECTORY ${_include_dir}) +TARGET_INCLUDE_DIRECTORIES( + Python::Python + INTERFACE ${_include_dir} +) +LIST(APPEND RV_DEPS_LIST Python::Python) + +ADD_DEPENDENCIES(dependencies ${_python3_target}-stage-target) + +SET(RV_DEPS_PYTHON3_VERSION + ${_python3_version} + CACHE INTERNAL "" FORCE +) +SET(RV_DEPS_PYSIDE2_VERSION + ${_pyside2_version} + CACHE INTERNAL "" FORCE +) diff --git a/cmake/dependencies/qt5.cmake b/cmake/dependencies/qt5.cmake new file mode 100644 index 000000000..f2587df47 --- /dev/null +++ b/cmake/dependencies/qt5.cmake @@ -0,0 +1,234 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +SET(RV_DEPS_QT5_LOCATION + "" + CACHE STRING "Path to pre-compiled Qt5" +) +SET(_target + RV_DEPS_QT5 +) +IF(NOT RV_DEPS_QT5_LOCATION + OR RV_DEPS_QT5_LOCATION STREQUAL "" +) + MESSAGE( + FATAL_ERROR + "Unable to build without a RV_DEPS_QT5_LOCATION. It is required to provide a working Qt 5.15 root path to build. Example: cmake .. -DRV_DEPS_QT5_LOCATION=/Users/rv/Qt/5.15.11/clang_64" + ) +ENDIF() + +FILE(GLOB QT5_CMAKE_DIRS ${RV_DEPS_QT5_LOCATION}/lib/cmake/*) +FOREACH( + QT5_CMAKE_DIR + ${QT5_CMAKE_DIRS} +) + IF(IS_DIRECTORY ${QT5_CMAKE_DIR}) + GET_FILENAME_COMPONENT(QT_COMPONENT_NAME ${QT5_CMAKE_DIR} NAME) + SET(CMAKE_PREFIX_PATH + ${CMAKE_PREFIX_PATH} ${QT5_CMAKE_DIR} + ) + SET(${QT_COMPONENT_NAME}_DIR + ${QT5_CMAKE_DIR} + CACHE INTERNAL "Path to ${QT_COMPONENT_NAME} CMake" + ) + ENDIF() +ENDFOREACH() + +# Testing if everything is alright. +FIND_PACKAGE( + Qt5 + COMPONENTS Core WebEngineCore Widgets + REQUIRED +) + +SET(_qt_copy_message + "Copying Qt into ${RV_STAGE_ROOT_DIR}" +) +MESSAGE(STATUS "${_qt_copy_message}") + +SET(RV_DEPS_QT5_VERSION + ${Qt5Core_VERSION_STRING} + CACHE STRING "Qt Version String" +) + +# Common to both Mac and Linux platforms +IF(RV_TARGET_DARWIN + OR RV_TARGET_LINUX +) + FILE( + GLOB _qt_plugins_dirs + RELATIVE ${RV_DEPS_QT5_LOCATION}/plugins + ${RV_DEPS_QT5_LOCATION}/plugins/* + ) + FOREACH( + _qt_plugin_dir + ${_qt_plugins_dirs} + ) + FILE( + COPY ${RV_DEPS_QT5_LOCATION}/plugins/${_qt_plugin_dir} + DESTINATION ${RV_STAGE_PLUGINS_QT_DIR} + ) + ENDFOREACH() +ENDIF() + +# Mac +IF(RV_TARGET_DARWIN) + SET(_qt5_lib_dir + ${RV_DEPS_QT5_LOCATION}/lib + ) + FILE( + GLOB libs_to_copy + RELATIVE ${_qt5_lib_dir} + ${_qt5_lib_dir}/* + ) + FOREACH( + lib_to_copy + ${libs_to_copy} + ) + IF(lib_to_copy MATCHES "framework(.dSYM)?") + FILE( + COPY ${_qt5_lib_dir}/${lib_to_copy} + DESTINATION ${RV_STAGE_FRAMEWORKS_DIR} + ) + ENDIF() + ENDFOREACH() +ENDIF() + +# Linux +IF(RV_TARGET_LINUX) + SET(RV_DEPS_QT5_LIB_DIR + ${RV_DEPS_QT5_LOCATION}/lib + ) + FILE( + GLOB libs_to_copy + RELATIVE ${RV_DEPS_QT5_LIB_DIR} + CONFIGURE_DEPENDS ${RV_DEPS_QT5_LIB_DIR}/* + ) + FOREACH( + lib_to_copy + ${libs_to_copy} + ) + FILE( + COPY ${RV_DEPS_QT5_LIB_DIR}/${lib_to_copy} + DESTINATION ${RV_STAGE_LIB_DIR} + ) + ENDFOREACH() + + MESSAGE(STATUS "Copying Qt libexec files ...") + FILE( + COPY "${RV_DEPS_QT5_LOCATION}/libexec" + DESTINATION "${RV_STAGE_ROOT_DIR}" + ) + + MESSAGE(STATUS "Copying Qt resources files ...") + FILE( + COPY "${RV_DEPS_QT5_LOCATION}/resources" + DESTINATION "${RV_STAGE_ROOT_DIR}" + ) + + MESSAGE(STATUS "Copying Qt translations files ...") + FILE( + COPY "${RV_DEPS_QT5_LOCATION}/translations" + DESTINATION "${RV_STAGE_ROOT_DIR}" + ) +ENDIF() + +# Windows + +IF(RV_TARGET_WINDOWS) + # Note: On windows, the Qt distribution has both the debug and release versions of its libs, dlls, and plugins into the same directories. This will prevent RV + # from working correctly. We need to only copy the ones matching the CMAKE_BUILD_TYPE + FUNCTION(COPY_ONLY_LIBS_MATCHING_BUILD_TYPE SRC_DIR DST_DIR) + + IF(NOT EXISTS (${DST_DIR})) + FILE(MAKE_DIRECTORY ${DST_DIR}) + ENDIF() + + # First make a list of problematic libs: the ones ending up with d (which means they end with dd in debug) + FILE( + GLOB _qt_dd_libs + RELATIVE ${SRC_DIR} + ${SRC_DIR}/*dd.* + ) + + FILE( + GLOB _qt_debug_libs + RELATIVE ${SRC_DIR} + ${SRC_DIR}/*d.* + ) + + # If we have dd libs then we need to remove the single d ones from the _qt_debug_libs + FOREACH( + _qt_dd_lib + ${_qt_dd_libs} + ) + # Determine the name of the associated single d lib so we can remove it from the _qt_debug_libs + STRING(REPLACE "dd." "d." _d_lib ${_qt_dd_lib}) + LIST(REMOVE_ITEM _qt_debug_libs ${_d_lib}) + ENDFOREACH() + + IF(CMAKE_BUILD_TYPE MATCHES "^Debug$") + SET(_qt_libs_to_copy + ${_qt_debug_libs} + ) + ELSE() + FILE( + GLOB _qt_libs_to_copy + RELATIVE ${SRC_DIR} + ${SRC_DIR}/* + ) + LIST(REMOVE_ITEM _qt_libs_to_copy ${_qt_debug_libs}) + ENDIF() + + FOREACH( + _qt_lib + ${_qt_libs_to_copy} + ) + IF(NOT IS_DIRECTORY "${SRC_DIR}/${_qt_lib}") + FILE(COPY_FILE ${SRC_DIR}/${_qt_lib} ${DST_DIR}/${_qt_lib} ONLY_IF_DIFFERENT) + ENDIF() + ENDFOREACH() + ENDFUNCTION() + + # Copy the Qt plugins + FILE( + GLOB _qt_plugins_dirs + RELATIVE ${RV_DEPS_QT5_LOCATION}/plugins + ${RV_DEPS_QT5_LOCATION}/plugins/* + ) + FOREACH( + _qt_plugin_dir + ${_qt_plugins_dirs} + ) + COPY_ONLY_LIBS_MATCHING_BUILD_TYPE(${RV_DEPS_QT5_LOCATION}/plugins/${_qt_plugin_dir} ${RV_STAGE_PLUGINS_QT_DIR}/${_qt_plugin_dir}) + ENDFOREACH() + + # Copy the Qt import libs + SET(RV_DEPS_QT5_LIB_DIR + ${RV_DEPS_QT5_LOCATION}/lib + ) + COPY_ONLY_LIBS_MATCHING_BUILD_TYPE(${RV_DEPS_QT5_LIB_DIR} ${RV_STAGE_LIB_DIR}) + + # Copy the Qt dlls + SET(RV_DEPS_QT5_BIN_DIR + ${RV_DEPS_QT5_LOCATION}/bin + ) + COPY_ONLY_LIBS_MATCHING_BUILD_TYPE(${RV_DEPS_QT5_BIN_DIR} ${RV_STAGE_BIN_DIR}) + + MESSAGE(STATUS "Copying Qt translations files ...") + FILE( + COPY "${RV_DEPS_QT5_LOCATION}/translations" + DESTINATION "${RV_STAGE_ROOT_DIR}" + ) + + MESSAGE(STATUS "Copying Qt resources files ...") + FILE( + COPY "${RV_DEPS_QT5_LOCATION}/resources" + DESTINATION "${RV_STAGE_ROOT_DIR}" + ) +ENDIF() + +MESSAGE(STATUS "${_qt_copy_message} -- DONE") diff --git a/cmake/dependencies/zlib.cmake b/cmake/dependencies/zlib.cmake new file mode 100644 index 000000000..7eb64be5d --- /dev/null +++ b/cmake/dependencies/zlib.cmake @@ -0,0 +1,170 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(ProcessorCount) # require CMake 3.15+ +PROCESSORCOUNT(_cpu_count) + +SET(_target + "RV_DEPS_ZLIB" +) + +SET(_version + "1.2.13" +) +SET(_download_url + "https://github.com/madler/zlib/archive/refs/tags/v${_version}.zip" +) + +SET(_download_hash + "fdedf0c8972a04a7c153dd73492d2d91" +) + +SET(_install_dir + ${RV_DEPS_BASE_DIR}/${_target}/install +) +SET(_include_dir + ${_install_dir}/include +) + +SET(_lib_dir + ${_install_dir}/lib +) +SET(_bin_dir + ${_install_dir}/bin +) + +SET(_make_command + make +) +IF(${RV_OSX_EMULATION}) + SET(_darwin_x86_64 + "arch" "${RV_OSX_EMULATION_ARCH}" + ) + SET(_make_command + ${_darwin_x86_64} ${_make_command} + ) +ENDIF() +IF(RV_TARGET_WINDOWS) + # MSYS2/CMake defaults to Ninja + SET(_make_command + ninja + ) +ENDIF() + +IF(RV_TARGET_WINDOWS) + IF(CMAKE_BUILD_TYPE MATCHES "^Debug$") + SET(_zlibname + "zlibd" + ) + ELSE() + SET(_zlibname + "zlib" + ) + ENDIF() +ELSE() + SET(_zlibname + "z" + ) +ENDIF() + +SET(_libname + ${CMAKE_SHARED_LIBRARY_PREFIX}${_zlibname}${CMAKE_SHARED_LIBRARY_SUFFIX} +) +SET(_libpath + ${_lib_dir}/${_libname} +) + +LIST(APPEND _zlib_byproducts ${_libpath}) + +IF(RV_TARGET_WINDOWS) + SET(_implibname + ${CMAKE_IMPORT_LIBRARY_PREFIX}${_zlibname}${CMAKE_IMPORT_LIBRARY_SUFFIX} + ) + SET(_implibpath + ${_lib_dir}/${_implibname} + ) + LIST(APPEND _zlib_byproducts ${_implibpath}) +ENDIF() + +EXTERNALPROJECT_ADD( + ${_target} + URL ${_download_url} + URL_MD5 ${_download_hash} + DOWNLOAD_NAME ${_target}_${_version}.zip + DOWNLOAD_DIR ${RV_DEPS_DOWNLOAD_DIR} + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + SOURCE_DIR ${RV_DEPS_BASE_DIR}/${_target}/src + INSTALL_DIR ${_install_dir} + CONFIGURE_COMMAND ${CMAKE_COMMAND} -DCMAKE_INSTALL_PREFIX=${_install_dir} -DCMAKE_OSX_ARCHITECTURES=${CMAKE_OSX_ARCHITECTURES} + -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} ${RV_DEPS_BASE_DIR}/${_target}/src + BUILD_COMMAND ${_make_command} -j${_cpu_count} -v + INSTALL_COMMAND ${_make_command} install + BUILD_IN_SOURCE TRUE + BUILD_ALWAYS FALSE + BUILD_BYPRODUCTS ${_zlib_byproducts} + USES_TERMINAL_BUILD TRUE +) + +FILE(MAKE_DIRECTORY "${_include_dir}") + +IF(RV_TARGET_WINDOWS) + ADD_CUSTOM_COMMAND( + TARGET ${_target} + POST_BUILD + COMMENT "Installing ${_target}'s libs and bin into ${RV_STAGE_LIB_DIR} and ${RV_STAGE_BIN_DIR}" + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_bin_dir} ${RV_STAGE_BIN_DIR} + ) + ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_BIN_DIR}/${_libname} + ) +ELSE() + ADD_CUSTOM_COMMAND( + COMMENT "Installing ${_target}'s libs into ${RV_STAGE_LIB_DIR}" + OUTPUT ${RV_STAGE_LIB_DIR}/${_libname} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${_lib_dir} ${RV_STAGE_LIB_DIR} + DEPENDS ${_target} + ) + ADD_CUSTOM_TARGET( + ${_target}-stage-target ALL + DEPENDS ${RV_STAGE_LIB_DIR}/${_libname} + ) +ENDIF() + +ADD_DEPENDENCIES(dependencies ${_target}-stage-target) + +ADD_LIBRARY(ZLIB::ZLIB SHARED IMPORTED GLOBAL) +ADD_DEPENDENCIES(ZLIB::ZLIB ${_target}) +SET_PROPERTY( + TARGET ZLIB::ZLIB + PROPERTY IMPORTED_LOCATION ${_libpath} +) +SET_PROPERTY( + TARGET ZLIB::ZLIB + PROPERTY IMPORTED_SONAME ${_libname} +) +IF(RV_TARGET_WINDOWS) + SET_PROPERTY( + TARGET ZLIB::ZLIB + PROPERTY IMPORTED_IMPLIB ${_implibpath} + ) +ENDIF() +TARGET_INCLUDE_DIRECTORIES( + ZLIB::ZLIB + INTERFACE ${_include_dir} +) +LIST(APPEND RV_DEPS_LIST ZLIB::ZLIB) + +SET(RV_DEPS_ZLIB_INCLUDE_DIR + ${_include_dir} + CACHE STRING "Path to installed includes for ${_target}" +) + +SET(RV_DEPS_ZLIB_VERSION + ${_version} + CACHE INTERNAL "" FORCE +) diff --git a/cmake/globals/rv_git.cmake b/cmake/globals/rv_git.cmake new file mode 100644 index 000000000..06629ab3f --- /dev/null +++ b/cmake/globals/rv_git.cmake @@ -0,0 +1,31 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# The following was inspired by the following post: https://cmake.org/pipermail/cmake/2018-October/068388.html +# +FIND_PROGRAM(_git git NO_CACHE REQUIRED) + +EXECUTE_PROCESS( + COMMAND # Should replace the 'COMMIT git log -1 --pretty=format:%h' statement used in the old Makefile + ${_git} rev-parse --short HEAD + RESULT_VARIABLE _result + OUTPUT_VARIABLE RV_GIT_COMMIT_SHORT_HASH + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY +) + +EXECUTE_PROCESS( + COMMAND # Should replace the 'COMMIT git log -1 --pretty=format:%h' statement used in the old Makefile + ${_git} rev-parse --abbrev-ref HEAD + RESULT_VARIABLE _result + OUTPUT_VARIABLE RV_GIT_BRANCH + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY +) + +MESSAGE(STATUS "Using build branch: ${RV_GIT_BRANCH}") +MESSAGE(STATUS "Using build hash: ${RV_GIT_COMMIT_SHORT_HASH}") diff --git a/cmake/globals/rv_globals.cmake b/cmake/globals/rv_globals.cmake new file mode 100644 index 000000000..526bb7145 --- /dev/null +++ b/cmake/globals/rv_globals.cmake @@ -0,0 +1,221 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(rv_git) +INCLUDE(rv_version) + +SET(RV_DEPS_DOWNLOAD_DIR + "${RV_DEPS_BASE_DIR}/downloads" + CACHE STRING "RV's 3rd party download cache location." +) +FILE(MAKE_DIRECTORY ${RV_DEPS_DOWNLOAD_DIR}) + +SET(RV_BUILD_ROOT + ${CMAKE_BINARY_DIR}/stage + CACHE STRING "RV's build output directory." +) +FILE(MAKE_DIRECTORY ${RV_BUILD_ROOT}) + +SET(RV_APP_ROOT + ${RV_BUILD_ROOT}/app + CACHE STRING "RV's build output app directory." +) +FILE(MAKE_DIRECTORY ${RV_APP_ROOT}) + +SET(RV_PACKAGES_DIR + ${RV_BUILD_ROOT}/packages + CACHE STRING "RV's build output packages output directory." +) +FILE(MAKE_DIRECTORY ${RV_PACKAGES_DIR}) + +IF(RV_TARGET_DARWIN) + SET(RV_STAGE_ROOT_DIR + ${RV_APP_ROOT}/RV.app/Contents + CACHE STRING "RV's build install root directory." + ) + SET(RV_STAGE_BIN_DIR + ${RV_STAGE_ROOT_DIR}/MacOS + ) +ELSE() + SET(RV_STAGE_ROOT_DIR + ${RV_APP_ROOT} + CACHE STRING "RV's build output directory." + ) + SET(RV_STAGE_BIN_DIR + ${RV_STAGE_ROOT_DIR}/bin + ) +ENDIF() + +FILE(MAKE_DIRECTORY ${RV_STAGE_ROOT_DIR}) +MESSAGE(STATUS "RV_STAGE_ROOT_DIR: ${RV_STAGE_ROOT_DIR}") + +IF(RV_TARGET_DARWIN) + SET(RV_STAGE_FRAMEWORKS_DIR + ${RV_STAGE_ROOT_DIR}/Frameworks + ) + FILE(MAKE_DIRECTORY ${RV_STAGE_FRAMEWORKS_DIR}) + MESSAGE(STATUS "RV_STAGE_FRAMEWORKS_DIR: ${RV_STAGE_FRAMEWORKS_DIR}") +ENDIF() + +IF(RV_TARGET_LINUX + OR RV_TARGET_WINDOWS +) + SET(RV_STAGE_SCRIPTS_DIR + ${RV_STAGE_ROOT_DIR}/scripts + ) + FILE(MAKE_DIRECTORY ${RV_STAGE_SCRIPTS_DIR}) +ENDIF() + +FILE(MAKE_DIRECTORY ${RV_STAGE_BIN_DIR}) +MESSAGE(STATUS "RV_STAGE_BIN_DIR: ${RV_STAGE_BIN_DIR}") + +SET(RV_STAGE_LIB_DIR + ${RV_STAGE_ROOT_DIR}/lib +) +FILE(MAKE_DIRECTORY ${RV_STAGE_LIB_DIR}) +MESSAGE(STATUS "RV_STAGE_LIB_DIR: ${RV_STAGE_LIB_DIR}") + +SET(RV_STAGE_INCLUDE_DIR + ${RV_STAGE_ROOT_DIR}/include +) +FILE(MAKE_DIRECTORY ${RV_STAGE_INCLUDE_DIR}) +MESSAGE(STATUS "RV_STAGE_INCLUDE_DIR: ${RV_STAGE_INCLUDE_DIR}") + +SET(RV_STAGE_SRC_DIR + ${RV_STAGE_ROOT_DIR}/src +) +FILE(MAKE_DIRECTORY ${RV_STAGE_SRC_DIR}) +MESSAGE(STATUS "RV_STAGE_SRC_DIR: ${RV_STAGE_SRC_DIR}") + +IF(RV_TARGET_DARWIN) + SET(RV_STAGE_RESOURCES_DIR + ${RV_STAGE_ROOT_DIR}/Resources + ) + SET(RV_STAGE_RESOURCES_ENGLISH_DIR + ${RV_STAGE_RESOURCES_DIR}/English.lproj + ) + FILE(MAKE_DIRECTORY ${RV_STAGE_RESOURCES_ENGLISH_DIR}) + MESSAGE(STATUS "RV_STAGE_RESOURCES_ENGLISH_DIR: ${RV_STAGE_RESOURCES_ENGLISH_DIR}") +ELSE() + SET(RV_STAGE_RESOURCES_DIR + ${RV_STAGE_ROOT_DIR}/resources + ) +ENDIF() + +FILE(MAKE_DIRECTORY ${RV_STAGE_RESOURCES_DIR}) +MESSAGE(STATUS "RV_STAGE_RESOURCES_DIR: ${RV_STAGE_RESOURCES_DIR}") + +IF(RV_TARGET_LINUX) + # For legacy reason, we keep the original lowercase name + SET(RV_STAGE_PLUGINS_DIR + ${RV_STAGE_ROOT_DIR}/plugins + ) +ELSE() + SET(RV_STAGE_PLUGINS_DIR + ${RV_STAGE_ROOT_DIR}/PlugIns + ) +ENDIF() +FILE(MAKE_DIRECTORY ${RV_STAGE_PLUGINS_DIR}) +MESSAGE(STATUS "RV_STAGE_PLUGINS_DIR: ${RV_STAGE_PLUGINS_DIR}") + +SET(RV_STAGE_PLUGINS_CONFIGFILES_DIR + ${RV_STAGE_PLUGINS_DIR}/ConfigFiles +) +FILE(MAKE_DIRECTORY ${RV_STAGE_PLUGINS_CONFIGFILES_DIR}) +MESSAGE(STATUS "RV_STAGE_PLUGINS_CONFIGFILES_DIR: ${RV_STAGE_PLUGINS_CONFIGFILES_DIR}") + +SET(RV_STAGE_PLUGINS_IMAGEFORMATS_DIR + ${RV_STAGE_PLUGINS_DIR}/ImageFormats +) +FILE(MAKE_DIRECTORY ${RV_STAGE_PLUGINS_IMAGEFORMATS_DIR}) +MESSAGE(STATUS "RV_STAGE_PLUGINS_IMAGEFORMATS_DIR: ${RV_STAGE_PLUGINS_IMAGEFORMATS_DIR}") + +SET(RV_STAGE_PLUGINS_MOVIEFORMATS_DIR + ${RV_STAGE_PLUGINS_DIR}/MovieFormats +) +FILE(MAKE_DIRECTORY ${RV_STAGE_PLUGINS_MOVIEFORMATS_DIR}) +MESSAGE(STATUS "RV_STAGE_PLUGINS_MOVIEFORMATS_DIR: ${RV_STAGE_PLUGINS_MOVIEFORMATS_DIR}") + +SET(RV_STAGE_PLUGINS_MU_DIR + ${RV_STAGE_PLUGINS_DIR}/Mu +) +FILE(MAKE_DIRECTORY ${RV_STAGE_PLUGINS_MU_DIR}) +MESSAGE(STATUS "RV_STAGE_PLUGINS_MU_DIR: ${RV_STAGE_PLUGINS_MU_DIR}") + +SET(RV_STAGE_PLUGINS_NODES_DIR + ${RV_STAGE_PLUGINS_DIR}/Nodes +) +FILE(MAKE_DIRECTORY ${RV_STAGE_PLUGINS_NODES_DIR}) +MESSAGE(STATUS "RV_STAGE_PLUGINS_NODES_DIR: ${RV_STAGE_PLUGINS_NODES_DIR}") + +SET(RV_STAGE_PLUGINS_OIIO_DIR + ${RV_STAGE_PLUGINS_DIR}/OIIO +) +FILE(MAKE_DIRECTORY ${RV_STAGE_PLUGINS_OIIO_DIR}) +MESSAGE(STATUS "RV_STAGE_PLUGINS_OIIO_DIR: ${RV_STAGE_PLUGINS_OIIO_DIR}") + +SET(RV_STAGE_PLUGINS_PACKAGES_DIR + ${RV_STAGE_PLUGINS_DIR}/Packages +) +FILE(MAKE_DIRECTORY ${RV_STAGE_PLUGINS_PACKAGES_DIR}) +MESSAGE(STATUS "RV_STAGE_PLUGINS_PACKAGES_DIR: ${RV_STAGE_PLUGINS_PACKAGES_DIR}") + +SET(RV_STAGE_PLUGINS_PROFILES_DIR + ${RV_STAGE_PLUGINS_DIR}/Profiles +) +FILE(MAKE_DIRECTORY ${RV_STAGE_PLUGINS_PROFILES_DIR}) +MESSAGE(STATUS "RV_STAGE_PLUGINS_PROFILES_DIR: ${RV_STAGE_PLUGINS_PROFILES_DIR}") + +SET(RV_STAGE_PLUGINS_PYTHON_DIR + ${RV_STAGE_PLUGINS_DIR}/Python +) +FILE(MAKE_DIRECTORY ${RV_STAGE_PLUGINS_PYTHON_DIR}) +MESSAGE(STATUS "RV_STAGE_PLUGINS_PYTHON_DIR: ${RV_STAGE_PLUGINS_PYTHON_DIR}") + +SET(RV_STAGE_PLUGINS_QT_DIR + ${RV_STAGE_PLUGINS_DIR}/Qt +) +FILE(MAKE_DIRECTORY ${RV_STAGE_PLUGINS_QT_DIR}) +MESSAGE(STATUS "RV_STAGE_PLUGINS_QT_DIR: ${RV_STAGE_PLUGINS_QT_DIR}") + +SET(RV_STAGE_PLUGINS_SUPPORTFILES_DIR + ${RV_STAGE_PLUGINS_DIR}/SupportFiles +) +FILE(MAKE_DIRECTORY ${RV_STAGE_PLUGINS_SUPPORTFILES_DIR}) +MESSAGE(STATUS "RV_STAGE_PLUGINS_SUPPORTFILES_DIR: ${RV_STAGE_PLUGINS_SUPPORTFILES_DIR}") + +SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY + "${RV_STAGE_LIB_DIR}" +) +SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY + "${RV_STAGE_LIB_DIR}" +) +SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY + "${RV_STAGE_BIN_DIR}" +) +IF(RV_TARGET_WINDOWS) + FOREACH( + OUTPUTCONFIG + ${CMAKE_CONFIGURATION_TYPES} + ) + STRING(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} + "${RV_STAGE_LIB_DIR}" + ) + SET(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUTCONFIG} + "${RV_STAGE_LIB_DIR}" + ) + SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} + "${RV_STAGE_BIN_DIR}" + ) + ENDFOREACH() +ENDIF() + +IF(${CMAKE_BUILD_TYPE} STREQUAL "Debug") + SET(RV_DEBUG_POSTFIX + "_d" + ) +ENDIF() diff --git a/cmake/globals/rv_version.cmake b/cmake/globals/rv_version.cmake new file mode 100644 index 000000000..a7da08371 --- /dev/null +++ b/cmake/globals/rv_version.cmake @@ -0,0 +1,42 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +SET(RV_VERSION_YEAR + "2023" + CACHE STRING "RV's year of release." +) +SET(RV_MAJOR_VERSION + "1" + CACHE STRING "RV's version major" +) +SET(RV_MINOR_VERSION + "0" + CACHE STRING "RV's version minor" +) +SET(RV_REVISION_NUMBER + "0" + CACHE STRING "RV's revision number" +) + +SET(RV_VERSION_STRING + "${RV_MAJOR_VERSION}.${RV_MINOR_VERSION}.${RV_REVISION_NUMBER}" +) + +IF(${CMAKE_BUILD_TYPE} STREQUAL "Debug") + SET(RV_RELEASE_DESCRIPTION + "Debug" + ) +ELSE() + # as checked 'src/lib/app/RvCommon/RvApplication.cpp' + SET(RV_RELEASE_DESCRIPTION + "RELEASE" + ) +ENDIF() + +SET(RV_INTERNAL_APPLICATION_NAME + "OpenRV" + CACHE STRING "RV's internal application name" +) diff --git a/cmake/install/CMakeLists.txt b/cmake/install/CMakeLists.txt new file mode 100644 index 000000000..b063fef43 --- /dev/null +++ b/cmake/install/CMakeLists.txt @@ -0,0 +1,54 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +MESSAGE(STATUS "Adding \"CMAKE_MINIMUM_REQUIRED(VERSION ${CMAKE_MINIMUM_REQUIRED_VERSION})\" to INSTALL") + +INSTALL(CODE "CMAKE_MINIMUM_REQUIRED(VERSION ${CMAKE_MINIMUM_REQUIRED_VERSION})") + +GET_CMAKE_PROPERTY(_variableNames VARIABLES) +LIST(SORT _variableNames) +FOREACH( + _variableName + ${_variableNames} +) + IF(_variableName MATCHES "^RV_") + IF(${_variableName}) + MESSAGE(STATUS "Adding VARIABLE ${_variableName} to INSTALL") + INSTALL(CODE "SET(${_variableName} ${${_variableName}})") + ENDIF() + ENDIF() +ENDFOREACH() + +MESSAGE(STATUS "Adding SCRIPT ${CMAKE_CURRENT_LIST_DIR}/pre_install.cmake to INSTALL") + +IF(RV_TARGET_DARWIN) + MESSAGE(STATUS "Adding SCRIPT ${CMAKE_CURRENT_LIST_DIR}/pre_install_darwin.cmake to INSTALL") + INSTALL(SCRIPT ${CMAKE_CURRENT_LIST_DIR}/pre_install_darwin.cmake) +ELSEIF(RV_TARGET_LINUX) + MESSAGE(STATUS "Adding SCRIPT ${CMAKE_CURRENT_LIST_DIR}/pre_install_linux.cmake to INSTALL") + INSTALL(SCRIPT ${CMAKE_CURRENT_LIST_DIR}/pre_install_linux.cmake) +ELSEIF(RV_TARGET_WINDOWS) + MESSAGE(STATUS "Adding SCRIPT ${CMAKE_CURRENT_LIST_DIR}/pre_install_windows.cmake to INSTALL") + INSTALL(SCRIPT ${CMAKE_CURRENT_LIST_DIR}/pre_install_windows.cmake) +ENDIF() + +INSTALL(SCRIPT ${CMAKE_CURRENT_LIST_DIR}/pre_install.cmake) + +MESSAGE(STATUS "Adding SCRIPT ${CMAKE_CURRENT_LIST_DIR}/install_darwin.cmake to INSTALL") +INSTALL(SCRIPT ${CMAKE_CURRENT_LIST_DIR}/install.cmake) + +MESSAGE(STATUS "Adding SCRIPT ${CMAKE_CURRENT_LIST_DIR}/post_install.cmake to INSTALL") +INSTALL(SCRIPT ${CMAKE_CURRENT_LIST_DIR}/post_install.cmake) +IF(RV_TARGET_DARWIN) + MESSAGE(STATUS "Adding SCRIPT ${CMAKE_CURRENT_LIST_DIR}/post_install_darwin.cmake to INSTALL") + INSTALL(SCRIPT ${CMAKE_CURRENT_LIST_DIR}/post_install_darwin.cmake) +ELSEIF(RV_TARGET_LINUX) + MESSAGE(STATUS "Adding SCRIPT ${CMAKE_CURRENT_LIST_DIR}/post_install_linux.cmake to INSTALL") + INSTALL(SCRIPT ${CMAKE_CURRENT_LIST_DIR}/post_install_linux.cmake) +ELSEIF(RV_TARGET_WINDOWS) + MESSAGE(STATUS "Adding SCRIPT ${CMAKE_CURRENT_LIST_DIR}/post_install_windows.cmake to INSTALL") + INSTALL(SCRIPT ${CMAKE_CURRENT_LIST_DIR}/post_install_windows.cmake) +ENDIF() diff --git a/cmake/install/install.cmake b/cmake/install/install.cmake new file mode 100644 index 000000000..10412907d --- /dev/null +++ b/cmake/install/install.cmake @@ -0,0 +1,54 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +MESSAGE(STATUS "Install prefix: \"${CMAKE_INSTALL_PREFIX}\"") +IF(NOT IS_ABSOLUTE ${CMAKE_INSTALL_PREFIX}) + MESSAGE(FATAL_ERROR "${CMAKE_INSTALL_PREFIX} is not an absolute path") +ENDIF() + +FILE( + GLOB_RECURSE FILES_TO_COPY + LIST_DIRECTORIES FALSE + RELATIVE ${RV_APP_ROOT} + ${RV_APP_ROOT}/* +) + +LIST(LENGTH FILES_TO_COPY FILES_TO_COPY_LENGTH) + +SET(CURRENT_FILE_INDEX + "0" +) +FOREACH( + FILE_TO_COPY + ${FILES_TO_COPY} +) + MATH( + EXPR CURRENT_FILE_INDEX "${CURRENT_FILE_INDEX}+1" + OUTPUT_FORMAT DECIMAL + ) + + MATH( + EXPR CURRENT_PERCENTAGE " ${CURRENT_FILE_INDEX} * 100 / ${FILES_TO_COPY_LENGTH} " + OUTPUT_FORMAT DECIMAL + ) + BEFORE_COPY(${RV_APP_ROOT}/${FILE_TO_COPY} SHOULD_INSTALL) + + IF(NOT SHOULD_INSTALL) + MESSAGE(STATUS "${CURRENT_PERCENTAGE}% -- Skipping\t ${RV_APP_ROOT}/${FILE_TO_COPY}") + CONTINUE() + ENDIF() + + GET_FILENAME_COMPONENT(DESTINATION_FOLDER ${CMAKE_INSTALL_PREFIX}/${FILE_TO_COPY} DIRECTORY) + MESSAGE(STATUS "${CURRENT_PERCENTAGE}% -- Installing ${CMAKE_INSTALL_PREFIX}/${FILE_TO_COPY}") + + FILE( + COPY ${RV_APP_ROOT}/${FILE_TO_COPY} + DESTINATION ${DESTINATION_FOLDER} + USE_SOURCE_PERMISSIONS + ) + + AFTER_COPY(${CMAKE_INSTALL_PREFIX}/${FILE_TO_COPY}) +ENDFOREACH() diff --git a/cmake/install/post_install.cmake b/cmake/install/post_install.cmake new file mode 100644 index 000000000..141a18a58 --- /dev/null +++ b/cmake/install/post_install.cmake @@ -0,0 +1,56 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# This module can be used to verify the contents of the installation folder. as a mean to double check previous operations. +# +# The first part replicates to some extents what's being done in the 'pre_install.cmake' script. +# + +SET(QT_GPL_COMPONENTS + charts + datavisualization + designer + linguist + designercomponent + designerconfig + networkauthorization + networkauth + virtualkeyboard + quickwebgl + webgl + platforminputcontext + design +) + +IF(CMAKE_INSTALL_PREFIX) + # + # Create a list of all installed files + FILE( + GLOB_RECURSE _all_installed_files + LIST_DIRECTORIES FALSE + RELATIVE ${CMAKE_INSTALL_PREFIX} + ${CMAKE_INSTALL_PREFIX}/* + ) + + # + # Loop through all components in all all installed files + MESSAGE(STATUS "Double checking installed files...") + FOREACH( + QT_GPL_COMPONENT + ${QT_GPL_COMPONENTS} + ) + FOREACH( + _file + ${_all_installed_files} + ) + STRING(TOLOWER "${_file}" _file_lower) + IF("${_file_lower}" MATCHES "${QT_GPL_COMPONENT}") + MESSAGE(FATAL_ERROR "Was not expecting the installation of the followin file: '${_file}'") + ENDIF() + ENDFOREACH() + ENDFOREACH() +ELSE() + MESSAGE(FATAL_ERROR "An install prefix was not set: '${CMAKE_INSTALL_PREFIX}'") +ENDIF() diff --git a/cmake/install/post_install_darwin.cmake b/cmake/install/post_install_darwin.cmake new file mode 100644 index 000000000..0f786122c --- /dev/null +++ b/cmake/install/post_install_darwin.cmake @@ -0,0 +1,5 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# diff --git a/cmake/install/post_install_linux.cmake b/cmake/install/post_install_linux.cmake new file mode 100644 index 000000000..0f786122c --- /dev/null +++ b/cmake/install/post_install_linux.cmake @@ -0,0 +1,5 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# diff --git a/cmake/install/post_install_windows.cmake b/cmake/install/post_install_windows.cmake new file mode 100644 index 000000000..0f786122c --- /dev/null +++ b/cmake/install/post_install_windows.cmake @@ -0,0 +1,5 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# diff --git a/cmake/install/pre_install.cmake b/cmake/install/pre_install.cmake new file mode 100644 index 000000000..b9bd9d972 --- /dev/null +++ b/cmake/install/pre_install.cmake @@ -0,0 +1,109 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +SET(QT_GPL_COMPONENTS + charts + datavisualization + designer + linguist + designercomponent + designerconfig + networkauthorization + networkauth + virtualkeyboard + quickwebgl + webgl + platforminputcontext + design +) + +FUNCTION(before_copy FILE_PATH RET_VAL) + FOREACH( + QT_GPL_COMPONENT + ${QT_GPL_COMPONENTS} + ) + STRING(TOLOWER "${FILE_PATH}" FILE_PATH_LOWER) + + MESSAGE(STATUS "Checking '${FILE_PATH_LOWER}' for '${QT_GPL_COMPONENT}'") + + IF(${FILE_PATH_LOWER} MATCHES "${QT_GPL_COMPONENT}") + SET(${RET_VAL} + "NO" + PARENT_SCOPE + ) + RETURN() + ENDIF() + ENDFOREACH() + + IF(FILE_PATH MATCHES "\\.prl$") + SET(${RET_VAL} + "NO" + PARENT_SCOPE + ) + RETURN() + ENDIF() + + IF(FILE_PATH MATCHES "\\.prl$" + OR FILE_PATH MATCHES "\\.o$" + OR FILE_PATH MATCHES "\\.a$" + OR FILE_PATH MATCHES "\\.la$" + ) + SET(${RET_VAL} + "NO" + PARENT_SCOPE + ) + RETURN() + ENDIF() + + IF(FILE_PATH MATCHES "${RV_STAGE_LIB_DIR}/cmake" + OR FILE_PATH MATCHES "${RV_STAGE_LIB_DIR}/engine" + OR FILE_PATH MATCHES "${RV_STAGE_LIB_DIR}/metatype" + OR FILE_PATH MATCHES "${RV_STAGE_LIB_DIR}/pkgconfig" + ) + SET(${RET_VAL} + "NO" + PARENT_SCOPE + ) + RETURN() + ENDIF() + + IF(FILE_PATH MATCHES "${RV_STAGE_BIN_DIR}/.*Test.*" + AND NOT FILE_PATH MATCHES "${RV_STAGE_BIN_DIR}/Qt5Test.*" + ) + SET(${RET_VAL} + "NO" + PARENT_SCOPE + ) + RETURN() + ENDIF() + + IF(COMMAND before_copy_platform) + BEFORE_COPY_PLATFORM(${FILE_PATH} RET_VAL_PLATFORM) + SET(${RET_VAL} + "${RET_VAL_PLATFORM}" + PARENT_SCOPE + ) + RETURN() + ELSE() + SET(${RET_VAL} + "YES" + PARENT_SCOPE + ) + RETURN() + ENDIF() + + SET(${RET_VAL} + "YES" + PARENT_SCOPE + ) + RETURN() +ENDFUNCTION() + +FUNCTION(after_copy FILE_PATH) + IF(COMMAND after_copy_platform) + AFTER_COPY_PLATFORM(${FILE_PATH}) + ENDIF() +ENDFUNCTION() diff --git a/cmake/install/pre_install_darwin.cmake b/cmake/install/pre_install_darwin.cmake new file mode 100644 index 000000000..0dd5ea38d --- /dev/null +++ b/cmake/install/pre_install_darwin.cmake @@ -0,0 +1,88 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +FUNCTION(before_copy_platform FILE_PATH RET_VAL) + IF(FILE_PATH MATCHES "\\.dSYM") + IF(CMAKE_INSTALL_CONFIG_NAME MATCHES "^Release$") + SET(${RET_VAL} + "NO" + PARENT_SCOPE + ) + RETURN() + ENDIF() + ENDIF() + + IF(FILE_PATH MATCHES "\\.DS_Store") + SET(${RET_VAL} + "NO" + PARENT_SCOPE + ) + RETURN() + ENDIF() + + SET(${RET_VAL} + "YES" + PARENT_SCOPE + ) + RETURN() +ENDFUNCTION() + +# It's really slow to try to fix the rpaths on all the files. This list contains extensions that are known to not be mach-o +SET(KNOWN_EXTENSIONS_TO_SKIP + ".h" + ".c" + ".C" + ".js" + ".mu" + ".rvpkg" + ".glsl" + ".hpp" + ".cpp" + ".json" + ".xml" + ".py" + ".pyc" + ".pyi" + ".pxd" + ".pyproject" + ".txt" + ".pak" + ".qm" + ".qmltypes" + ".plist" + ".png" + ".jpg" + ".jpeg" + ".aifc" + ".aiff" + ".au" + ".wav" + ".decTest" + ".qml" + ".qmlc" + ".metainfo" + ".mesh" + ".init" + ".zip" + ".md" +) + +FUNCTION(after_copy_platform FILE_PATH) + + IF(IS_SYMLINK ${FILE_PATH}) + MESSAGE(STATUS "\tSkipping RPATH cleanup on symlinks") + RETURN() + ENDIF() + + GET_FILENAME_COMPONENT(FILE_EXT ${FILE_PATH} LAST_EXT) + IF(FILE_EXT IN_LIST KNOWN_EXTENSIONS_TO_SKIP) + MESSAGE(STATUS "\tSkipping RPATH cleanup due to file extension") + RETURN() + ENDIF() + + EXECUTE_PROCESS(COMMAND python3 ${CMAKE_SOURCE_DIR}/src/build/remove_absolute_rpath.py --target ${FILE_PATH} COMMAND_ERROR_IS_FATAL ANY) + +ENDFUNCTION() diff --git a/cmake/install/pre_install_linux.cmake b/cmake/install/pre_install_linux.cmake new file mode 100644 index 000000000..dd6c33e7b --- /dev/null +++ b/cmake/install/pre_install_linux.cmake @@ -0,0 +1,37 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +FUNCTION(before_copy_platform FILE_PATH RET_VAL) + IF(FILE_PATH MATCHES "\\.debug") + IF(CMAKE_INSTALL_CONFIG_NAME MATCHES "^Release$") + SET(${RET_VAL} + "NO" + PARENT_SCOPE + ) + RETURN() + ENDIF() + ENDIF() + + IF(FILE_PATH MATCHES "${RV_STAGE_LIB_DIR}/libcrypto" + OR FILE_PATH MATCHES "${RV_STAGE_LIB_DIR}/libssl" + ) + SET(${RET_VAL} + "NO" + PARENT_SCOPE + ) + RETURN() + ENDIF() + + SET(${RET_VAL} + "YES" + PARENT_SCOPE + ) + RETURN() +ENDFUNCTION() + +FUNCTION(after_copy_platform FILE_PATH) + +ENDFUNCTION() diff --git a/cmake/install/pre_install_windows.cmake b/cmake/install/pre_install_windows.cmake new file mode 100644 index 000000000..a8ac4943a --- /dev/null +++ b/cmake/install/pre_install_windows.cmake @@ -0,0 +1,27 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +FUNCTION(before_copy_platform FILE_PATH RET_VAL) + IF(FILE_PATH MATCHES "\\.pdb$") + IF(CMAKE_INSTALL_CONFIG_NAME MATCHES "^Release$") + SET(${RET_VAL} + "NO" + PARENT_SCOPE + ) + RETURN() + ENDIF() + ENDIF() + + SET(${RET_VAL} + "YES" + PARENT_SCOPE + ) + RETURN() +ENDFUNCTION() + +FUNCTION(after_copy_platform FILE_PATH) + +ENDFUNCTION() diff --git a/cmake/macros/rv_lex.cmake b/cmake/macros/rv_lex.cmake new file mode 100644 index 000000000..d8fce5782 --- /dev/null +++ b/cmake/macros/rv_lex.cmake @@ -0,0 +1,133 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(rv_sed) +FIND_PROGRAM(_lex flex NO_CACHE REQUIRED) + +EXECUTE_PROCESS( + COMMAND bash "-c" "${_lex} --version | cut -d '.' -f 2" + OUTPUT_VARIABLE _flex_minor_version + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +EXECUTE_PROCESS( + COMMAND bash "-c" "${_lex} --version | grep Apple | wc -l | ${_sed} 's/ //g'" + OUTPUT_VARIABLE _flex_apple + OUTPUT_STRIP_TRAILING_WHITESPACE +) + +SET(RV_FLEX_MINOR_VERSION + ${_flex_minor_version} + CACHE STRING "The Flex/Lex tool minor version used for building RV." +) +SET(RV_FLEX_APPLE + ${_flex_apple} + CACHE BOOL "Wether or not we're building with an Apple-compiled version of Flex/Lex tool." +) + +# +# 01234567890123456789012345678901234567890123456789012345678901234567890123456789 ! lex_it : wraps a call to Flex/Lex utility. +# +# The functon is checking specified parameters allowing clearer error messages from the CMake context. The function also allows an input '.sed' file to be +# specified so the lexer output can be patched. +# +# All of this is happening at configure time and should produce an output file that should typically be added to the module's source files. +# +# \param:PARAM1 PARAM1 specify the fooness of the function \param:PARAM2 PARAM2 should always be 42 \group:GROUP1 GROUP1 is a list of project to foo +# \group:GROUP2 This group represent optional project to pass to bar +# +FUNCTION(lex_it) + + SET(flags) + SET(args) + SET(listArgs + YY_PREFIX INPUT_FILE OUTPUT_FILE INPUT_SED_FILE FLAGS OUTPUT_DIR + ) + SET(_result + "" + ) + + CMAKE_PARSE_ARGUMENTS(arg "${flags}" "${args}" "${listArgs}" ${ARGN}) + + MESSAGE(DEBUG "******************************************************") + MESSAGE(DEBUG "lex_it params:") + MESSAGE(DEBUG "--- YY_PREFIX : '${arg_YY_PREFIX}'") + MESSAGE(DEBUG "--- FLAGS : '${arg_FLAGS}'") + MESSAGE(DEBUG "--- INPUT_FILE : '${arg_INPUT_FILE}'") + MESSAGE(DEBUG "--- OUTPUT_FILE : '${arg_OUTPUT_FILE}'") + MESSAGE(DEBUG "--- INPUT_SED_FILE : '${arg_INPUT_SED_FILE}'") + MESSAGE(DEBUG "--- CMAKE_CURRENT_BINARY_DIR : '${CMAKE_CURRENT_BINARY_DIR}'") + MESSAGE(DEBUG "--- UNPARSED_ARGUMENTS : '${arg_UNPARSED_ARGUMENTS}'") + MESSAGE(DEBUG "--- KEYWORDS_MISSING_VALUES : '${arg_KEYWORDS_MISSING_VALUES}'") + + IF(NOT arg_YY_PREFIX) + MESSAGE(FATAL ERROR "The 'YY_PREFIX' parameter was not specified.") + ENDIF() + + IF(NOT arg_INPUT_FILE) + MESSAGE(FATAL ERROR "The 'INPUT_FILE' parameter was not specified.") + ENDIF() + + IF(NOT arg_OUTPUT_FILE) + MESSAGE(FATAL ERROR "The 'OUTPUT_FILE' parameter was not specified.") + ENDIF() + + GET_FILENAME_COMPONENT(input_file_name ${arg_INPUT_FILE} NAME) + SET(input_timestamp_file + ${CMAKE_CURRENT_BINARY_DIR}/${input_file_name}.timestamp + ) + FILE(TIMESTAMP ${arg_INPUT_FILE} _input_file_timestamp) + IF(EXISTS ${arg_OUTPUT_DIR}/${arg_OUTPUT_FILE} + AND EXISTS ${input_timestamp_file} + ) + FILE(READ ${input_timestamp_file} _saved_input_file_timestamp) + IF(_input_file_timestamp STREQUAL _saved_input_file_timestamp) + RETURN() + ENDIF() + ENDIF() + FILE( + WRITE ${input_timestamp_file} + ${_input_file_timestamp} + ) + + IF(NOT arg_FLAGS) + MESSAGE(STATUS "Lexer flags not specified, using RV defaults.") + SET(arg_FLAGS + --debug --prefix=${arg_YY_PREFIX} + ) + ENDIF() + + IF(RV_VERBOSE_INVOCATION) + LIST(APPEND arg_FLAGS "--verbose") + ENDIF() + + MESSAGE(DEBUG "Lexer flags: ${arg_FLAGS}") + MESSAGE(STATUS "Generating lexer source file --> '${arg_OUTPUT_FILE}'") + + FILE(MAKE_DIRECTORY ${arg_OUTPUT_DIR}) + + # + # Generate the lexer source source file + EXECUTE_PROCESS( + WORKING_DIRECTORY ${arg_OUTPUT_DIR} + COMMAND ${_lex} ${arg_FLAGS} --outfile "${arg_OUTPUT_FILE}" "${arg_INPUT_FILE}" COMMAND_ECHO STDOUT + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _result_code COMMAND_ERROR_IS_FATAL ANY + ) + + IF(arg_INPUT_SED_FILE) + SED_IT( + INPUT_SED_FILE + ${arg_INPUT_SED_FILE} + INPUT_FILE + ${arg_OUTPUT_FILE} + OUTPUT_DIR + ${arg_OUTPUT_DIR} + OUTPUT_FILE + ${arg_OUTPUT_FILE} + ) + ENDIF() +ENDFUNCTION() diff --git a/cmake/macros/rv_quote_file.cmake b/cmake/macros/rv_quote_file.cmake new file mode 100644 index 000000000..c753e91d4 --- /dev/null +++ b/cmake/macros/rv_quote_file.cmake @@ -0,0 +1,155 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +FIND_PROGRAM(_quote_file quoteFile HINT ${CMAKE_SOURCE_DIR}/src/build/quoteFile.py NO_CACHE REQUIRED) + +FUNCTION(quote_files OUTPUT_LIST) + + SET(flags) + SET(args) + SET(listArgs + INPUT_LIST SUFFIX FLAGS + ) + SET(_cooked_list + "" + ) + + CMAKE_PARSE_ARGUMENTS(arg "${flags}" "${args}" "${listArgs}" ${ARGN}) + + MESSAGE(DEBUG "******************************************************") + MESSAGE(DEBUG "quote_files params:") + MESSAGE(DEBUG "--- FLAGS : '${arg_FLAGS}'") + MESSAGE(DEBUG "--- INPUT_LIST : '${arg_INPUT_LIST}'") + MESSAGE(DEBUG "--- SUFFIX : '${arg_SUFFIX}'") + MESSAGE(DEBUG "--- UNPARSED_ARGUMENTS : '${arg_UNPARSED_ARGUMENTS}'") + MESSAGE(DEBUG "--- KEYWORDS_MISSING_VALUES : '${arg_KEYWORDS_MISSING_VALUES}'") + + # + # Rename all of the specified entries as *_.cpp + FOREACH( + _entry + ${arg_INPUT_LIST} + ) + CMAKE_PATH(GET _entry STEM _stem) + SET(_src_name + "${CMAKE_CURRENT_SOURCE_DIR}/${arg_SUFFIX}/${_entry}.${arg_SUFFIX}" + ) + SET(_dest_name + "${CMAKE_CURRENT_BINARY_DIR}/${_stem}_${arg_SUFFIX}.cpp" + ) + + GET_FILENAME_COMPONENT(input_file_name ${_src_name} NAME) + SET(input_timestamp_file + ${CMAKE_CURRENT_BINARY_DIR}/${input_file_name}.timestamp + ) + FILE(TIMESTAMP ${_src_name} _input_file_timestamp) + IF(EXISTS ${_dest_name} + AND EXISTS ${input_timestamp_file} + ) + FILE(READ ${input_timestamp_file} _saved_input_file_timestamp) + IF(_input_file_timestamp STREQUAL _saved_input_file_timestamp) + LIST(APPEND _cooked_list ${_dest_name}) + CONTINUE() + ENDIF() + ENDIF() + FILE( + WRITE ${input_timestamp_file} + ${_input_file_timestamp} + ) + + MESSAGE(STATUS "Creating '${arg_SUFFIX}' file: '${_src_name}' --> '${_dest_name}'") + EXECUTE_PROCESS(COMMAND python3 ${_quote_file} "${_src_name}" "${_dest_name}" "${_stem}_${arg_SUFFIX}" COMMAND_ERROR_IS_FATAL ANY) + + LIST(APPEND _cooked_list ${_dest_name}) + ENDFOREACH() + + # This returns the new list to the caller scope + SET(${OUTPUT_LIST} + ${_cooked_list} + PARENT_SCOPE + ) +ENDFUNCTION() + +# TODO: add documentation +FUNCTION(quote_file OUTPUT_FILENAME) + + SET(flags) + SET(args) + SET(listArgs + INPUT_FILENAME CPP_SYMBOL FLAGS + ) + SET(_cooked_list + "" + ) + + CMAKE_PARSE_ARGUMENTS(arg "${flags}" "${args}" "${listArgs}" ${ARGN}) + + MESSAGE(DEBUG "******************************************************") + MESSAGE(DEBUG "quote_file params:") + MESSAGE(DEBUG "--- FLAGS : '${arg_FLAGS}'") + MESSAGE(DEBUG "--- INPUT_FILENAME : '${arg_INPUT_FILENAME}'") + MESSAGE(DEBUG "--- OUTPUT_FILENAME : '${OUTPUT_FILENAME}'") + MESSAGE(DEBUG "--- CPP_SYMBOL : '${arg_CPP_SYMBOL}'") + MESSAGE(DEBUG "--- UNPARSED_ARGUMENTS : '${arg_UNPARSED_ARGUMENTS}'") + MESSAGE(DEBUG "--- KEYWORDS_MISSING_VALUES : '${arg_KEYWORDS_MISSING_VALUES}'") + + CMAKE_PATH(GET arg_INPUT_FILENAME STEM _stem) + SET(_src_name + "${CMAKE_CURRENT_SOURCE_DIR}/${arg_INPUT_FILENAME}" + ) + SET(_dest_name + "${CMAKE_CURRENT_BINARY_DIR}/${_stem}.cpp" + ) + + GET_FILENAME_COMPONENT(input_file_name ${arg_INPUT_FILENAME} NAME) + SET(input_timestamp_file + ${CMAKE_CURRENT_BINARY_DIR}/${input_file_name}.timestamp + ) + FILE(TIMESTAMP ${arg_INPUT_FILENAME} _input_file_timestamp) + IF(EXISTS ${input_timestamp_file}) + FILE(READ ${input_timestamp_file} _saved_input_file_timestamp) + IF(${_input_file_timestamp} EQUAL ${_saved_input_file_timestamp}) + SET(${OUTPUT_FILENAME} + ${_dest_name} + PARENT_SCOPE + ) + RETURN() + ENDIF() + ENDIF() + FILE( + WRITE ${input_timestamp_file} + ${_input_file_timestamp} + ) + + # This check is about creating file if it doesn't exists + IF(NOT EXISTS ${_dest_name}) + MESSAGE(STATUS "Quoting '${_src_name}' as '${_dest_name}'") + EXECUTE_PROCESS( + COMMAND python3 ${_quote_file} "${_src_name}" "${_dest_name}" "${arg_CPP_SYMBOL}" + RESULT_VARIABLE _result + ) + IF(_result) + MESSAGE(FATAL_ERROR "Couldn't create file from '${_src_name}'") + ENDIF() + ENDIF() + + # This check is about confirming the above did created a file + IF(EXISTS ${_dest_name}) + # File exists ... check size + FILE(SIZE ${_dest_name} _size) + IF(_size LESS_EQUAL 50) # arbitrary, but we ain't expecting anything that small + MESSAGE(FATAL_ERROR "Generated file '${_dest_name}' is smaller than expected, size:${_size}") + ENDIF() + + # All checks are green returns the generated filename back to caller + SET(${OUTPUT_FILENAME} + ${_dest_name} + PARENT_SCOPE + ) + ELSE() + MESSAGE(FATAL_ERROR "Couldn't find the created file '${_dest_name}'") + ENDIF() +ENDFUNCTION() diff --git a/cmake/macros/rv_sed.cmake b/cmake/macros/rv_sed.cmake new file mode 100644 index 000000000..963fc9b71 --- /dev/null +++ b/cmake/macros/rv_sed.cmake @@ -0,0 +1,101 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +FIND_PROGRAM(_sed sed NO_CACHE REQUIRED) + +# +# 01234567890123456789012345678901234567890123456789012345678901234567890123456789 ! sed_it2 : wraps a call to the 'sed' utility. +# +# The functon is checking specified parameters allowing clearer error messages from the CMake context. +# +# Various RV supported systems have different version of the 'sed' utility having issues with the '-i' switch and/or usage of same input and output filename. +# This led to the convoluted scheme (below) having to 'sed' to a temporary output file then rename the temporary file to user-specified output filename. +# Although more complicated and more steps, it did seem to have the mre consistent behavior accross the supported platforms. +# +# All of this is happening at configure time and should produce and output file typically used as module's source files. +# +# \param:PARAM1 PARAM1 specify the fooness of the function \param:PARAM2 PARAM2 should always be 42 \group:GROUP1 GROUP1 is a list of project to foo +# \group:GROUP2 This group represent optional project to pass to bar +# +FUNCTION(sed_it) + + SET(flags) + SET(args) + SET(listArgs + INPUT_FILE INPUT_SED_FILE OUTPUT_FILE FLAGS OUTPUT_DIR + ) + + CMAKE_PARSE_ARGUMENTS(arg "${flags}" "${args}" "${listArgs}" ${ARGN}) + + MESSAGE(DEBUG "******************************************************") + MESSAGE(DEBUG "sed_it params:") + MESSAGE(DEBUG "--- FLAGS : '${arg_FLAGS}'") + MESSAGE(DEBUG "--- INPUT_FILE : '${arg_INPUT_FILE}'") + MESSAGE(DEBUG "--- INPUT_SED_FILE: '${arg_INPUT_SED_FILE}'") + MESSAGE(DEBUG "--- OUTPUT_FILE : '${arg_OUTPUT_FILE}'") + MESSAGE(DEBUG "--- UNPARSED_ARGUMENTS : '${arg_UNPARSED_ARGUMENTS}'") + MESSAGE(DEBUG "--- KEYWORDS_MISSING_VALUES : '${arg_KEYWORDS_MISSING_VALUES}'") + + IF(NOT arg_INPUT_SED_FILE) + MESSAGE(FATAL ERROR "The 'INPUT_SED_FILE' parameter was not specified.") + ENDIF() + + IF(NOT arg_INPUT_FILE) + MESSAGE(FATAL ERROR "The 'INPUT_FILE' parameter was not specified.") + ENDIF() + + IF(NOT arg_OUTPUT_FILE) + MESSAGE(FATAL ERROR "The 'OUTPUT_FILE' parameter was not specified.") + ENDIF() + + # Adds a target with the given name that executes the given commands. The target has no output file and is always considered out of date even if the commands + # try to create a file with the name of the target. + CMAKE_PATH(GET arg_OUTPUT_FILE FILENAME _filename) + CMAKE_PATH(GET arg_OUTPUT_FILE PARENT_PATH _path) + MESSAGE(DEBUG "_filename: '${_filename}'") + MESSAGE(DEBUG "_path: '${_path}'") + + GET_FILENAME_COMPONENT(input_file_name ${arg_INPUT_FILE} NAME) + SET(input_timestamp_file + ${CMAKE_CURRENT_BINARY_DIR}/${input_file_name}.timestamp + ) + FILE(TIMESTAMP ${arg_INPUT_FILE} _input_file_timestamp) + IF(EXISTS${arg_OUTPUT_DIR}/${arg_OUTPUT_FILE} + AND EXISTS ${input_timestamp_file} + ) + FILE(READ ${input_timestamp_file} _saved_input_file_timestamp) + IF(_input_file_timestamp STREQUAL _saved_input_file_timestamp) + RETURN() + ENDIF() + ENDIF() + FILE( + WRITE ${input_timestamp_file} + ${_input_file_timestamp} + ) + + FILE(MAKE_DIRECTORY ${arg_OUTPUT_DIR}) + + SET(_temp + "${arg_OUTPUT_DIR}/_temp_${_filename}" + ) + + MESSAGE(DEBUG "_temp: '${_temp}'") + + # + # Actual SED operation + EXECUTE_PROCESS( + WORKING_DIRECTORY ${arg_OUTPUT_DIR} + COMMAND ${_sed} -f ${arg_INPUT_SED_FILE} ${arg_INPUT_FILE} COMMAND_ECHO STDOUT + OUTPUT_FILE ${_temp} + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _result_code COMMAND_ERROR_IS_FATAL ANY + ) + + # Finally, rename the temporary file to the user-specified output filename + FILE(REMOVE ${arg_OUTPUT_DIR}/${arg_OUTPUT_FILE}) + FILE(RENAME ${_temp} ${arg_OUTPUT_DIR}/${arg_OUTPUT_FILE}) + +ENDFUNCTION() diff --git a/cmake/macros/rv_stage.cmake b/cmake/macros/rv_stage.cmake new file mode 100644 index 000000000..efa208a45 --- /dev/null +++ b/cmake/macros/rv_stage.cmake @@ -0,0 +1,662 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(rv_version) + +# +# Create & populate a list of all the shared libraries for later testing. +MACRO(ADD_SHARED_LIBRARY_LIST new_entry) + LIST(APPEND _shared_libraries "${new_entry}") + LIST(REMOVE_DUPLICATES _shared_libraries) + SET(_shared_libraries + ${_shared_libraries} + CACHE INTERNAL "" + ) +ENDMACRO() + +ADD_CUSTOM_TARGET(executables) +ADD_CUSTOM_TARGET(executables_with_plugins) +ADD_CUSTOM_TARGET(shared_libraries) +ADD_CUSTOM_TARGET(libraries) +ADD_CUSTOM_TARGET(mu_source_modules) +ADD_CUSTOM_TARGET(python_source_modules) +ADD_CUSTOM_TARGET(compiled_python_source_modules) +ADD_CUSTOM_TARGET(mu_plugins) +ADD_CUSTOM_TARGET(python_plugins) +ADD_CUSTOM_TARGET(packages) +ADD_CUSTOM_TARGET(installed_packages) +ADD_CUSTOM_TARGET(movie_formats) +ADD_CUSTOM_TARGET(installed_movie_formats) +ADD_CUSTOM_TARGET(image_formats) +ADD_CUSTOM_TARGET(installed_image_formats) +ADD_CUSTOM_TARGET(oiio_plugins) + +FUNCTION(rv_stage) + SET(flags + "" + ) + SET(args + "" + ) + SET(listArgs + TYPE TARGET FILES PATH + ) + + CMAKE_PARSE_ARGUMENTS(arg "${flags}" "${args}" "${listArgs}" ${ARGN}) + + MESSAGE(DEBUG "******************************************************") + MESSAGE(DEBUG "rv_stage params:") + MESSAGE(DEBUG "--- TYPE : '${arg_TYPE}'") + MESSAGE(DEBUG "--- TARGET : '${arg_TARGET}'") + MESSAGE(DEBUG "--- FILES : '${arg_FILES}'") + MESSAGE(DEBUG "--- PATH : '${arg_PATH}'") + MESSAGE(DEBUG "--- UNPARSED_ARGUMENTS : '${arg_UNPARSED_ARGUMENTS}'") + MESSAGE(DEBUG "--- KEYWORDS_MISSING_VALUES : '${arg_KEYWORDS_MISSING_VALUES}'") + + IF(NOT arg_TARGET) + MESSAGE(FATAL_ERROR "The 'TARGET' parameter was not specified.") + ENDIF() + + IF(NOT arg_TYPE) + MESSAGE(FATAL_ERROR "The 'TYPE' parameter was not specified.") + ENDIF() + + IF(RV_TARGET_LINUX) + IF(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${arg_TARGET}.wrapper) + + ADD_CUSTOM_COMMAND( + TARGET ${arg_TARGET} + POST_BUILD + COMMENT "Building ${RV_STAGE_BIN_DIR}/${arg_TARGET}.wrapper" + COMMAND ${CMAKE_COMMAND} -E rename "$" "$.bin" + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/${arg_TARGET}.wrapper "$" + COMMAND chmod +x "$" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + ENDIF() + ENDIF() + + IF(RV_TARGET_DARWIN) + IF(TARGET ${arg_TARGET}) + GET_TARGET_PROPERTY(_native_target_type ${arg_TARGET} TYPE) + IF(_native_target_type STREQUAL "EXECUTABLE") + ADD_CUSTOM_COMMAND( + COMMENT "Fixing ${arg_TARGET}'s RPATHs" TARGET ${arg_TARGET} POST_BUILD + COMMAND ${CMAKE_INSTALL_NAME_TOOL} -add_rpath "@executable_path/../Frameworks" "$" + COMMAND ${CMAKE_INSTALL_NAME_TOOL} -add_rpath "@executable_path/../lib" "$" + ) + ENDIF() + + IF(_native_target_type STREQUAL "EXECUTABLE" + OR _native_target_type STREQUAL "SHARED_LIBRARY" + ) + FOREACH( + dep + ${RV_DEPS_LIST} + ) + GET_PROPERTY( + dep_file_path + TARGET ${dep} + PROPERTY LOCATION + ) + GET_FILENAME_COMPONENT(dep_file_name ${dep_file_path} NAME) + ADD_CUSTOM_COMMAND( + COMMENT "Fixing ${dep_file_name}'s rpath in ${arg_TARGET}" TARGET ${arg_TARGET} POST_BUILD + COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change "${dep_file_path}" "@rpath/${dep_file_name}" "$" + ) + ENDFOREACH() + ENDIF() + ENDIF() + ENDIF() + + IF(${arg_TYPE} STREQUAL "SHARED_LIBRARY") + GET_TARGET_PROPERTY(_native_target_type ${arg_TARGET} TYPE) + IF(NOT _native_target_type STREQUAL "SHARED_LIBRARY") + MESSAGE(FATAL_ERROR "\"${arg_TARGET}\" ${arg_TYPE} should be a SHARED_LIBRARY, not a ${_native_target_type}") + ENDIF() + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${RV_STAGE_LIB_DIR}" + C_VISIBILITY_PRESET default + CXX_VISIBILITY_PRESET default + ) + IF(RV_TARGET_WINDOWS) + FOREACH( + OUTPUTCONFIG + ${CMAKE_CONFIGURATION_TYPES} + ) + STRING(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${RV_STAGE_BIN_DIR}" + ) + ENDFOREACH() + ENDIF() + + ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) + + ADD_DEPENDENCIES(shared_libraries ${arg_TARGET}) + + ELSEIF(${arg_TYPE} STREQUAL "LIBRARY") + GET_TARGET_PROPERTY(_native_target_type ${arg_TARGET} TYPE) + IF(NOT _native_target_type STREQUAL "STATIC_LIBRARY") + MESSAGE(FATAL_ERROR "\"${arg_TARGET}\" ${arg_TYPE} should be a STATIC_LIBRARY, not a ${_native_target_type}") + ENDIF() + + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES ARCHIVE_OUTPUT_DIRECTORY "${RV_STAGE_LIB_DIR}" + ) + IF(RV_TARGET_WINDOWS) + FOREACH( + OUTPUTCONFIG + ${CMAKE_CONFIGURATION_TYPES} + ) + STRING(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES ARCHIVE_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${RV_STAGE_LIB_DIR}" + ) + ENDFOREACH() + ENDIF() + + ADD_DEPENDENCIES(libraries ${arg_TARGET}) + + ELSEIF(${arg_TYPE} STREQUAL "MU_SOURCE_MODULE") + FOREACH( + _file + ${arg_FILES} + ) + SET(input_file + ${CMAKE_CURRENT_SOURCE_DIR}/${_file} + ) + SET(output_file + ${RV_STAGE_PLUGINS_MU_DIR}/${_file} + ) + + IF(IS_DIRECTORY ${input_file}) + LIST(APPEND ${arg_TARGET}_mu_source_module_directory_input ${input_file}) + + FILE( + GLOB_RECURSE output_dir_files LIST_DIRECTORY FALSE + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + "${input_file}/*" + ) + FOREACH( + output_dir_file + ${output_dir_files} + ) + LIST(APPEND ${arg_TARGET}_mu_source_module_directory_output ${RV_STAGE_PLUGINS_MU_DIR}/${output_dir_file}) + ENDFOREACH() + + ELSE() + LIST(APPEND ${arg_TARGET}_mu_source_module_file_input ${input_file}) + LIST(APPEND ${arg_TARGET}_mu_source_module_file_output ${output_file}) + ENDIF() + ENDFOREACH() + + FOREACH( + directory + ${${arg_TARGET}_mu_source_module_directory_input} + ) + GET_FILENAME_COMPONENT(directory_name ${directory} NAME) + FILE(GLOB_RECURSE directory_files LIST_DIRECTORY FALSE "${directory}/*") + FOREACH( + file + ${directory_files} + ) + + FILE(RELATIVE_PATH relative_file ${directory} ${file}) + + LIST(APPEND ${directory}_depends ${file}) + LIST(APPEND ${directory}_output ${RV_STAGE_PLUGINS_MU_DIR}/${directory_name}/${relative_file}) + + LIST(APPEND ${arg_TARGET}_mu_source_module_directory_output ${RV_STAGE_PLUGINS_MU_DIR}/${output_dir_file}) + ENDFOREACH() + + ADD_CUSTOM_COMMAND( + COMMENT "Copying ${arg_TARGET} Python Source Module Directory '${directory_name}'" + OUTPUT ${${directory}_output} + DEPENDS ${${directory}_depends} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${directory} ${RV_STAGE_PLUGINS_MU_DIR}/${directory_name} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + ENDFOREACH() + + IF(${arg_TARGET}_mu_source_module_file_input) + ADD_CUSTOM_COMMAND( + COMMENT "Copying ${arg_TARGET} Python Source Module Files" + OUTPUT ${${arg_TARGET}_mu_source_module_file_output} + DEPENDS ${${arg_TARGET}_mu_source_module_file_input} + COMMAND ${CMAKE_COMMAND} -E copy ${${arg_TARGET}_mu_source_module_file_input} ${RV_STAGE_PLUGINS_MU_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + ENDIF() + + ADD_CUSTOM_TARGET( + mu-source-${arg_TARGET} + DEPENDS ${${arg_TARGET}_mu_source_module_file_output} ${${arg_TARGET}_mu_source_module_directory_output} + ) + + ADD_DEPENDENCIES(mu_source_modules mu-source-${arg_TARGET}) + ELSEIF(${arg_TYPE} STREQUAL "PYTHON_SOURCE_MODULE") + FOREACH( + _file + ${arg_FILES} + ) + SET(input_file + ${CMAKE_CURRENT_SOURCE_DIR}/${_file} + ) + SET(output_file + ${RV_STAGE_PLUGINS_PYTHON_DIR}/${_file} + ) + + IF(IS_DIRECTORY ${input_file}) + LIST(APPEND ${arg_TARGET}_python_source_module_directory_input ${input_file}) + + FILE( + GLOB_RECURSE output_dir_files LIST_DIRECTORY FALSE + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + "${input_file}/*" + ) + FOREACH( + output_dir_file + ${output_dir_files} + ) + LIST(APPEND ${arg_TARGET}_python_source_module_directory_output ${RV_STAGE_PLUGINS_PYTHON_DIR}/${output_dir_file}) + ENDFOREACH() + + ELSE() + LIST(APPEND ${arg_TARGET}_python_source_module_file_input ${input_file}) + LIST(APPEND ${arg_TARGET}_python_source_module_file_output ${output_file}) + ENDIF() + ENDFOREACH() + + FOREACH( + directory + ${${arg_TARGET}_python_source_module_directory_input} + ) + GET_FILENAME_COMPONENT(directory_name ${directory} NAME) + FILE(GLOB_RECURSE directory_files LIST_DIRECTORY FALSE "${directory}/*") + FOREACH( + file + ${directory_files} + ) + + FILE(RELATIVE_PATH relative_file ${directory} ${file}) + + LIST(APPEND ${directory}_depends ${file}) + LIST(APPEND ${directory}_output ${RV_STAGE_PLUGINS_PYTHON_DIR}/${directory_name}/${relative_file}) + + LIST(APPEND ${arg_TARGET}_python_source_module_directory_output ${RV_STAGE_PLUGINS_PYTHON_DIR}/${output_dir_file}) + ENDFOREACH() + + ADD_CUSTOM_COMMAND( + COMMENT "Copying ${arg_TARGET} Python Source Module Directory '${directory_name}'" + OUTPUT ${${directory}_output} + DEPENDS ${${directory}_depends} + COMMAND ${CMAKE_COMMAND} -E copy_directory ${directory} ${RV_STAGE_PLUGINS_PYTHON_DIR}/${directory_name} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + ENDFOREACH() + + IF(${arg_TARGET}_python_source_module_file_input) + ADD_CUSTOM_COMMAND( + COMMENT "Copying ${arg_TARGET} Python Source Module Files" + OUTPUT ${${arg_TARGET}_python_source_module_file_output} + DEPENDS ${${arg_TARGET}_python_source_module_file_input} + COMMAND ${CMAKE_COMMAND} -E copy ${${arg_TARGET}_python_source_module_file_input} ${RV_STAGE_PLUGINS_PYTHON_DIR} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + ENDIF() + + ADD_CUSTOM_TARGET( + python-source-${arg_TARGET} + DEPENDS ${${arg_TARGET}_python_source_module_file_output} ${${arg_TARGET}_python_source_module_directory_output} + ) + + ADD_DEPENDENCIES(python_source_modules python-source-${arg_TARGET}) + + ELSEIF(${arg_TYPE} STREQUAL "MU_PLUGIN") + GET_TARGET_PROPERTY(_native_target_type ${arg_TARGET} TYPE) + IF(NOT _native_target_type STREQUAL "SHARED_LIBRARY") + MESSAGE(FATAL_ERROR "\"${arg_TARGET}\" ${arg_TYPE} should be a SHARED_LIBRARY, not a ${_native_target_type}") + ENDIF() + + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES PREFIX "" + SUFFIX ".so" + LIBRARY_OUTPUT_DIRECTORY "${RV_STAGE_PLUGINS_MU_DIR}" + C_VISIBILITY_PRESET default + CXX_VISIBILITY_PRESET default + ) + IF(RV_TARGET_WINDOWS) + FOREACH( + OUTPUTCONFIG + ${CMAKE_CONFIGURATION_TYPES} + ) + STRING(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES PREFIX "" + SUFFIX ".so" + RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${RV_STAGE_PLUGINS_MU_DIR}" + ) + ENDFOREACH() + ENDIF() + + ADD_DEPENDENCIES(mu_plugins ${arg_TARGET}) + + ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) + + ELSEIF(${arg_TYPE} STREQUAL "PYTHON_PLUGIN") + GET_TARGET_PROPERTY(_native_target_type ${arg_TARGET} TYPE) + IF(NOT _native_target_type STREQUAL "SHARED_LIBRARY") + MESSAGE(FATAL_ERROR "\"${arg_TARGET}\" ${arg_TYPE} should be a SHARED_LIBRARY, not a ${_native_target_type}") + ENDIF() + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES PREFIX "" + SUFFIX ".so" + LIBRARY_OUTPUT_DIRECTORY "${RV_STAGE_PLUGINS_PYTHON_DIR}" + C_VISIBILITY_PRESET default + CXX_VISIBILITY_PRESET default + ) + IF(RV_TARGET_WINDOWS) + FOREACH( + OUTPUTCONFIG + ${CMAKE_CONFIGURATION_TYPES} + ) + # Note: Python expects modules to have the _d.pyd suffix in Debug on Windows + STRING(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES PREFIX "" + SUFFIX "${RV_DEBUG_POSTFIX}.pyd" + RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${RV_STAGE_PLUGINS_PYTHON_DIR}" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}./ + ) + ENDFOREACH() + ENDIF() + + ADD_DEPENDENCIES(python_plugins ${arg_TARGET}) + + ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) + + ELSEIF(${arg_TYPE} STREQUAL "RVPKG") + SET(_package_file + PACKAGE + ) + FILE( + GLOB _files + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + * + ) + + LIST(REMOVE_ITEM _files Makefile CMakeLists.txt ${_package_file}) + + IF(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${_package_file}) + MESSAGE(FATAL_ERROR "Couldn't find expected package descriptor file: '${_package_file}'") + ENDIF() + + EXECUTE_PROCESS( + COMMAND bash -c "cat ${_package_file} | grep version: | grep --only-matching -e '[0-9.]*'" + RESULT_VARIABLE _result + OUTPUT_VARIABLE _pkg_version + OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + IF(_result + AND NOT _result EQUAL 0 + ) + MESSAGE(FATAL_ERROR "Error retrieving version field from '${_package_file}'") + ELSE() + MESSAGE(DEBUG "Found version for '${arg_TARGET}.rvpkg' package version ${_pkg_version} ...") + ENDIF() + + SET(_package_filename + "${RV_PACKAGES_DIR}/${arg_TARGET}-${_pkg_version}.rvpkg" + ) + + LIST(APPEND RV_PACKAGE_LIST "${RV_PACKAGES_DIR}/${arg_TARGET}-${_pkg_version}.rvpkg") + LIST(REMOVE_DUPLICATES RV_PACKAGE_LIST) + SET(RV_PACKAGE_LIST + ${RV_PACKAGE_LIST} + CACHE INTERNAL "" + ) + + LIST(APPEND INSTALLED_RV_PACKAGE_LIST "${RV_STAGE_PLUGINS_PACKAGES_DIR}/${arg_TARGET}-${_pkg_version}.rvpkg") + LIST(REMOVE_DUPLICATES INSTALLED_RV_PACKAGE_LIST) + SET(INSTALLED_RV_PACKAGES + ${INSTALLED_RV_PACKAGE_LIST} + CACHE INTERNAL "" + ) + + ADD_CUSTOM_COMMAND( + COMMENT "Creating ${_package_filename} ..." + OUTPUT ${_package_filename} + DEPENDS ${_files} ${_package_file} + COMMAND zip -v -J ${_package_filename} ${_files} ${_package_file} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + + ADD_CUSTOM_TARGET( + ${arg_TARGET}-${_pkg_version}.rvpkg ALL + DEPENDS ${_package_filename} + ) + + ADD_DEPENDENCIES(packages ${arg_TARGET}-${_pkg_version}.rvpkg) + + ELSEIF(${arg_TYPE} STREQUAL "IMAGE_FORMAT") + GET_TARGET_PROPERTY(_native_target_type ${arg_TARGET} TYPE) + IF(NOT _native_target_type STREQUAL "SHARED_LIBRARY") + MESSAGE(FATAL_ERROR "\"${arg_TARGET}\" ${arg_TYPE} should be a SHARED_LIBRARY, not a ${_native_target_type}") + ENDIF() + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${RV_STAGE_PLUGINS_IMAGEFORMATS_DIR}" + PREFIX "" + C_VISIBILITY_PRESET default + CXX_VISIBILITY_PRESET default + ) + IF(RV_TARGET_WINDOWS) + FOREACH( + OUTPUTCONFIG + ${CMAKE_CONFIGURATION_TYPES} + ) + STRING(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${RV_STAGE_PLUGINS_IMAGEFORMATS_DIR}" + ) + ENDFOREACH() + ENDIF() + + ADD_DEPENDENCIES(image_formats ${arg_TARGET}) + + ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) + + ELSEIF(${arg_TYPE} STREQUAL "MOVIE_FORMAT") + GET_TARGET_PROPERTY(_native_target_type ${arg_TARGET} TYPE) + IF(NOT _native_target_type STREQUAL "SHARED_LIBRARY") + MESSAGE(FATAL_ERROR "\"${arg_TARGET}\" ${arg_TYPE} should be a SHARED_LIBRARY, not a ${_native_target_type}") + ENDIF() + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${RV_STAGE_PLUGINS_MOVIEFORMATS_DIR}" + PREFIX "" + C_VISIBILITY_PRESET default + CXX_VISIBILITY_PRESET default + ) + IF(RV_TARGET_WINDOWS) + FOREACH( + OUTPUTCONFIG + ${CMAKE_CONFIGURATION_TYPES} + ) + STRING(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${RV_STAGE_PLUGINS_MOVIEFORMATS_DIR}" + ) + ENDFOREACH() + ENDIF() + + ADD_DEPENDENCIES(movie_formats ${arg_TARGET}) + + ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) + + ELSEIF(${arg_TYPE} STREQUAL "OIIO_PLUGIN") + GET_TARGET_PROPERTY(_native_target_type ${arg_TARGET} TYPE) + IF(NOT _native_target_type STREQUAL "SHARED_LIBRARY") + MESSAGE(FATAL_ERROR "\"${arg_TARGET}\" ${arg_TYPE} should be a SHARED_LIBRARY, not a ${_native_target_type}") + ENDIF() + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${RV_STAGE_PLUGINS_OIIO_DIR}" + PREFIX "" + C_VISIBILITY_PRESET default + CXX_VISIBILITY_PRESET default + ) + IF(RV_TARGET_WINDOWS) + FOREACH( + OUTPUTCONFIG + ${CMAKE_CONFIGURATION_TYPES} + ) + STRING(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${RV_STAGE_PLUGINS_OIIO_DIR}" + PREFIX "" + ) + ENDFOREACH() + ENDIF() + + ADD_DEPENDENCIES(oiio_plugins ${arg_TARGET}) + + ADD_SHARED_LIBRARY_LIST(${arg_TARGET}) + + ELSEIF(${arg_TYPE} STREQUAL "EXECUTABLE") + GET_TARGET_PROPERTY(_native_target_type ${arg_TARGET} TYPE) + IF(NOT _native_target_type STREQUAL "EXECUTABLE") + MESSAGE(FATAL_ERROR "\"${arg_TARGET}\" ${arg_TYPE} should be a EXECUTABLE, not a ${_native_target_type}") + ENDIF() + + ADD_DEPENDENCIES(executables ${arg_TARGET}) + + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${RV_STAGE_BIN_DIR}" + ) + IF(RV_TARGET_WINDOWS) + FOREACH( + OUTPUTCONFIG + ${CMAKE_CONFIGURATION_TYPES} + ) + STRING(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${RV_STAGE_BIN_DIR}" + ) + ENDFOREACH() + ENDIF() + + ADD_DEPENDENCIES(${arg_TARGET} dependencies) + + ELSEIF(${arg_TYPE} STREQUAL "EXECUTABLE_WITH_PLUGINS") + GET_TARGET_PROPERTY(_native_target_type ${arg_TARGET} TYPE) + IF(NOT _native_target_type STREQUAL "EXECUTABLE") + MESSAGE(FATAL_ERROR "\"${arg_TARGET}\" ${arg_TYPE} should be a EXECUTABLE, not a ${_native_target_type}") + ENDIF() + + ADD_DEPENDENCIES(executables_with_plugins ${arg_TARGET}) + + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${RV_STAGE_BIN_DIR}" + ) + IF(RV_TARGET_WINDOWS) + FOREACH( + OUTPUTCONFIG + ${CMAKE_CONFIGURATION_TYPES} + ) + STRING(TOUPPER ${OUTPUTCONFIG} OUTPUTCONFIG) + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES RUNTIME_OUTPUT_DIRECTORY_${OUTPUTCONFIG} "${RV_STAGE_BIN_DIR}" + ) + ENDFOREACH() + ENDIF() + + IF(RV_TARGET_LINUX) + # Allow plugins that call dlopen to read app symbols + TARGET_LINK_OPTIONS(${arg_TARGET} PRIVATE "-export-dynamic") + ENDIF() + + ADD_DEPENDENCIES( + ${arg_TARGET} + mu_source_modules + python_source_modules + mu_plugins + python_plugins + packages + installed_packages + movie_formats + image_formats + oiio_plugins + dependencies + ) + + ELSEIF(${arg_TYPE} STREQUAL "MAIN_EXECUTABLE") + GET_TARGET_PROPERTY(_native_target_type ${arg_TARGET} TYPE) + IF(NOT _native_target_type STREQUAL "EXECUTABLE") + MESSAGE(FATAL_ERROR "\"${arg_TARGET}\" ${arg_TYPE} should be a EXECUTABLE, not a ${_native_target_type}") + ENDIF() + + IF(RV_TARGET_DARWIN) + SET_TARGET_PROPERTIES( + ${arg_TARGET} + PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${RV_APP_ROOT}" + MACOSX_BUNDLE TRUE + OUTPUT_NAME ${_target} + MACOSX_BUNDLE_BUNDLE_NAME ${_target} + MACOSX_BUNDLE_EXECUTABLE_NAME ${_target} + RESOURCE "${arg_FILES}" + MACOSX_BUNDLE_GUI_IDENTIFIER "com.autodesk.${_target}" + MACOSX_BUNDLE_SHORT_VERSION_STRING "${RV_MAJOR_VERSION}.${RV_MINOR_VERSION}.${RV_REVISION_NUMBER}" + MACOSX_BUNDLE_BUNDLE_VERSION "${RV_MAJOR_VERSION}.${RV_MINOR_VERSION}.${RV_REVISION_NUMBER}" + MACOSX_BUNDLE_ICON_FILE "${_target}.icns" + MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/Info.plist" + ) + ELSEIF(RV_TARGET_LINUX) + # Allow plugins that call dlopen to read app symbols + TARGET_LINK_OPTIONS(${arg_TARGET} PRIVATE "-export-dynamic") + ENDIF() + + ADD_DEPENDENCIES( + ${arg_TARGET} + mu_source_modules + python_source_modules + compiled_python_source_modules + mu_plugins + python_plugins + packages + installed_packages + movie_formats + installed_movie_formats + image_formats + installed_image_formats + oiio_plugins + shared_libraries + executables + executables_with_plugins + dependencies + ) + ELSE() + MESSAGE(FATAL_ERROR "Unsupported installation type: '${arg_TYPE}' in '${CMAKE_CURRENT_SOURCE_DIR}'") + ENDIF() + +ENDFUNCTION() diff --git a/cmake/macros/rv_yacc.cmake b/cmake/macros/rv_yacc.cmake new file mode 100644 index 000000000..e38c87649 --- /dev/null +++ b/cmake/macros/rv_yacc.cmake @@ -0,0 +1,127 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +FUNCTION(yacc_it) + + FIND_PROGRAM(_yacc bison NO_CACHE REQUIRED) + FIND_PROGRAM(_sed sed NO_CACHE REQUIRED) + + SET(flags) + SET(args) + SET(listArgs + YY_PREFIX INPUT_FILE OUTPUT_FILE INPUT_SED_FILE FLAGS OUTPUT_DIR + ) + + CMAKE_PARSE_ARGUMENTS(arg "${flags}" "${args}" "${listArgs}" ${ARGN}) + + MESSAGE(DEBUG "******************************************************") + MESSAGE(DEBUG "yacc_it params:") + MESSAGE(DEBUG "--- YY_PREFIX : '${arg_YY_PREFIX}'") + MESSAGE(DEBUG "--- FLAGS : '${arg_FLAGS}'") + MESSAGE(DEBUG "--- INPUT_FILE : '${arg_INPUT_FILE}'") + MESSAGE(DEBUG "--- OUTPUT_FILE : '${arg_OUTPUT_FILE}'") + MESSAGE(DEBUG "--- INPUT_SED_FILE : '${arg_INPUT_SED_FILE}'") + MESSAGE(DEBUG "--- UNPARSED_ARGUMENTS : '${arg_UNPARSED_ARGUMENTS}'") + MESSAGE(DEBUG "--- KEYWORDS_MISSING_VALUES : '${arg_KEYWORDS_MISSING_VALUES}'") + + IF(NOT arg_YY_PREFIX) + MESSAGE(FATAL ERROR "The 'YY_PREFIX' parameter was not specified.") + ENDIF() + + IF(NOT arg_INPUT_FILE) + MESSAGE(FATAL ERROR "The 'INPUT_FILE' parameter was not specified.") + ENDIF() + + IF(NOT arg_OUTPUT_FILE) + MESSAGE(FATAL ERROR "The 'OUTPUT_FILE' parameter was not specified.") + ENDIF() + + GET_FILENAME_COMPONENT(input_file_name ${arg_INPUT_FILE} NAME) + SET(input_timestamp_file + ${CMAKE_CURRENT_BINARY_DIR}/${input_file_name}.timestamp + ) + FILE(TIMESTAMP ${arg_INPUT_FILE} _input_file_timestamp) + IF(EXISTS ${arg_OUTPUT_DIR}/${arg_OUTPUT_FILE} + AND EXISTS ${input_timestamp_file} + ) + FILE(READ ${input_timestamp_file} _saved_input_file_timestamp) + IF(_input_file_timestamp STREQUAL _saved_input_file_timestamp) + RETURN() + ENDIF() + ENDIF() + FILE( + WRITE ${input_timestamp_file} + ${_input_file_timestamp} + ) + + IF(NOT arg_FLAGS) + MESSAGE(STATUS "Parser generator flags not specified, using RV defaults.") + # GNU bison generates parsers for LALR(1) grammars. + # + # -d, --defines also produce a header file -t, --debug instrument the parser for debugging -v, --verbose same as + # `--report=state' -p, --name-prefix=PREFIX prepend PREFIX to the external symbols -y, --yacc emulate POSIX yacc + # + # set(arg_FLAGS --yacc --debug --defines --name-prefix=${arg_YY_PREFIX}) + SET(arg_FLAGS + --debug --defines --name-prefix=${arg_YY_PREFIX} + ) + ENDIF() + IF(RV_VERBOSE_INVOCATION) + LIST(APPEND arg_FLAGS "--verbose") + ENDIF() + MESSAGE(DEBUG "Parser flags: ${arg_FLAGS}") + + # + # After parser generation, we need to copy the generated .hpp to .h Using the '-defines' switch, the bison tool generate a .hpp with same base name as the + # input file. We're expecting a .h in the code base, we therefore need to copy the generated include file. Note that on Windows, the parser generated .cpp + # file will include the .hpp file so we cannot simply rename it + CMAKE_PATH(GET arg_INPUT_FILE STEM _stem_name) + SET(_yacc_generated_include_filename + "${_stem_name}.hpp" + ) + SET(_renamed_include_filename + "${_stem_name}.h" + ) + MESSAGE(DEBUG "_renamed_include_filename: '${_renamed_include_filename}'") + + FILE(MAKE_DIRECTORY ${arg_OUTPUT_DIR}) + + # + # Generate parser source source file + EXECUTE_PROCESS( + WORKING_DIRECTORY ${arg_OUTPUT_DIR} + COMMAND ${_yacc} ${arg_FLAGS} --output=${arg_OUTPUT_FILE} ${arg_INPUT_FILE} COMMAND_ECHO STDOUT + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _result_code COMMAND_ERROR_IS_FATAL ANY + ) + + # + # Copy the generated .hpp to a .h file + EXECUTE_PROCESS( + WORKING_DIRECTORY ${arg_OUTPUT_DIR} + COMMAND ${CMAKE_COMMAND} -E copy ${arg_OUTPUT_DIR}/${_yacc_generated_include_filename} ${arg_OUTPUT_DIR}/${_renamed_include_filename} COMMAND_ECHO STDOUT + OUTPUT_STRIP_TRAILING_WHITESPACE + RESULT_VARIABLE _result_code + ) + MESSAGE(DEBUG "copy result code: '${_result_code}'") + IF(NOT ${_result_code} EQUAL 0) + MESSAGE(FATAL_ERROR "copy result code '${_result_code}'") + ENDIF() + + IF(arg_INPUT_SED_FILE) + SED_IT( + INPUT_SED_FILE + ${arg_INPUT_SED_FILE} + INPUT_FILE + ${arg_OUTPUT_FILE} + OUTPUT_DIR + ${arg_OUTPUT_DIR} + OUTPUT_FILE + ${arg_OUTPUT_FILE} + ) + ENDIF() + +ENDFUNCTION() diff --git a/doc/build_system/build_errors.md b/doc/build_system/build_errors.md new file mode 100644 index 000000000..dcb9a8c96 --- /dev/null +++ b/doc/build_system/build_errors.md @@ -0,0 +1,163 @@ +# Build Errors and Solutions + +### Mu::Parse(char const*, Mu::NodeAssembler*) + +```text +Undefined symbols for architecture x86_64: + "Mu::Parse(char const*, Mu::NodeAssembler*)", referenced from: + Mu::MuLangContext::evalText(char const*, char const*, Mu::Process*, std::__1::vector > const&) in libMuLang.a(MuLangContext.cpp.o) + Mu::MuLangContext::evalFile(char const*, Mu::Process*, std::__1::vector > const&) in libMuLang.a(MuLangContext.cpp.o) + Mu::MuLangContext::parseType(char const*, Mu::Process*) in libMuLang.a(MuLangContext.cpp.o) + Mu::MuLangContext::parseStream(Mu::Process*, std::__1::basic_istream >&, char const*) in libMuLang.a(MuLangContext.cpp.o) +ld: symbol(s) not found for architecture x86_64 +clang: error: linker command failed with exit code 1 (use -v to see invocation) +``` + +### Numerous undeclared identifier 'YYMUFlexLexer' Errors + +```text +FlexLexer.cpp:2429:10: error: use of undeclared identifier 'YYMUFlexLexer' + void yyFlexLexer::yy_load_buffer_state() + ^ +``` + +**Solution:** Most likely, the YY prefix wasn't set properly or at all + +### (macOS) from ranlib tool: XYZ has no symbols + +```text +[ 85%] Building CXX object src/lib/files/Gto/CMakeFiles/Gto.dir/Parser.cpp.o +[ 85%] Linking CXX static library libGto.a +/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: libGto.a(FlexLexer.cpp.o) has no symbols +/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/ranlib: file: libGto.a(FlexLexer.cpp.o) has no symbols +[100%] Built target Gto +``` + +**Solution:** Most likely an empty source file was compiled. + +### Linker Error, Undefined symbols "Mu::Parse(char const*, Mu::NodeAssembler*)" + +```text +Undefined symbols for architecture x86_64: + "Mu::Parse(char const*, Mu::NodeAssembler*)", referenced from: + Mu::MuLangContext::evalText(char const*, char const*, Mu::Process*, std::__1::vector > const&) in libMuLang.a(MuLangContext.cpp.o) + Mu::MuLangContext::evalFile(char const*, Mu::Process*, std::__1::vector > const&) in libMuLang.a(MuLangContext.cpp.o) + Mu::MuLangContext::parseType(char const*, Mu::Process*) in libMuLang.a(MuLangContext.cpp.o) + Mu::MuLangContext::parseStream(Mu::Process*, std::__1::basic_istream >&, char const*) in libMuLang.a(MuLangContext.cpp.o) +ld: symbol(s) not found for architecture x86_64 + +``` + +**Solution:** Check that Lexer/Parser compiled source aren't empty! Compiling an empty source file won't generate an error but would endup causing linker to complain du to missing code. This situation did occurred with `sed` command being to write-back to same file. The mitigation solution was to `sed` to a temporary file then rename the temporary file to the expected output filename. + +### Multiple 'error: unknown type name 'string' reported in system files + +```text +In file included from /OpenRV/src/lib/graphics/TwkGLF/GLVideoDevice.cpp:5: +In file included from /OpenRV/src/lib/graphics/TwkGLF/GLVideoDevice.h:7: +In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/string:519: +In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/__debug:14: +In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/iosfwd:98: +In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/__mbstate_t.h:29: +In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/wchar.h:123: +In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/wchar.h:91: +In file included from /OpenRV/src/lib/base/TwkMath/time.h:7: +In file included from /OpenRV/src/lib/base/TwkMath/math.h:12: +In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/algorithm:653: +In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/functional:500: +In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/__functional/function.h:20: +In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/__memory/shared_ptr.h:22: +In file included from /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/__memory/allocator.h:18: +/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/stdexcept:83:32: error: unknown type name 'string' + explicit logic_error(const string&); + ^ +/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/stdexcept:106:34: error: unknown type name 'string' + explicit runtime_error(const string&); + ^ +/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/stdexcept:126:59: error: unknown type name 'string' + _LIBCPP_INLINE_VISIBILITY explicit domain_error(const string& __s) : logic_error(__s) {} + ^ +/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk/usr/include/c++/v1/stdexcept:139:63: error: unknown type name 'string' + _LIBCPP_INLINE_VISIBILITY explicit invalid_argument(const string& __s) : logic_error(__s) {} +``` + +### (Linux) /usr/bin/env: ‘python’: No such file or directory + +```text +env: 'python': No such file or directory +CMake Error at cmake/macros/rv_quote_file.cmake:103 (message): + Couldn't create file from 'rv_about.html' +Call Stack (most recent call first): + src/lib/app/RvCommon/CMakeLists.txt:58 (quote_file2) +``` + +**Solution:** We need a `python` command available. + +The newer Ubuntu, Centos & Rocky system usually won't have `python` out of the box, they have a `python3` interpreter. + +One solution is to instyall `pipenv` and start a local virtual environment. That will give you a `p[ython` command. + +### (Linux) pyconfig.h: No such file or directory + +Should be in `/home/nmontmarquette/local/cache/RV_build/RV_DEPS_DESKTOP_PACKAGE/src/Python3/Release/include/python3.9/pyconfig.h` + +/home/nmontmarquette/local/cache/RV_build/RV_DEPS_DESKTOP_PACKAGE/src/Python3/Release/bin/python3.9 + +### (Linux) error: libaio.h: No such file or directory + +```text +/OpenRV/src/lib/base/TwkUtil/FileStream.cpp:170:10: fatal error: libaio.h: No such file or directory + #include + ^~~~~~~~~~ +``` + +**Solution:** + +```bash +sudo dnf install libaio-devel +``` + +### (Rocky Linux) + +```text +/usr/bin/ld: ../../../lib/mu/Mu/libMu.a(Thread.cpp.o): undefined reference to symbol 'pthread_attr_getstacksize@@GLIBC_2.2.5' +//usr/lib64/libpthread.so.0: error adding symbols: DSO missing from command line +collect2: error: ld returned 1 exit status +make[2]: *** [src/bin/apps/rv/CMakeFiles/rv.dir/build.make:228: src/bin/apps/rv/rv] Error 1 +make[1]: *** [CMakeFiles/Makefile2:2525: src/bin/apps/rv/CMakeFiles/rv.dir/all] Error 2 +make: *** [Makefile:91: all] Error 2 +``` + +### (Linux any Distro) +```text +/opt/rh/devtoolset-8/root/usr/libexec/gcc/x86_64-redhat-linux/8/ld: ../../../lib/graphics/TwkGLF/libTwkGLF.a(GLFBO.cpp.o): undefined reference to symbol 'glBindFramebufferEXT' +//lib64/libGL.so.1: error adding symbols: DSO missing from command line +collect2: error: ld returned 1 exit status +make[3]: *** [src/bin/apps/rv/CMakeFiles/rv.dir/build.make:236: src/bin/apps/rv/rv] Error 1 +make[2]: *** [CMakeFiles/Makefile2:2497: src/bin/apps/rv/CMakeFiles/rv.dir/all] Error 2 +make[1]: *** [CMakeFiles/Makefile2:2504: src/bin/apps/rv/CMakeFiles/rv.dir/rule] Error 2 +make: *** [Makefile:254: rv] Error 2 +[nmontmarquette@CentOS79 rv_build]$ +``` + +**Solution:** + +Ref.: [Resolve "DSO missing from command line" error](https://zhangboyi.gitlab.io/post/2020-09-14-resolve-dso-missing-from-command-line-error/) +Ref.: [error adding symbols: DSO missing from command line](https://stackoverflow.com/questions/19901934/libpthread-so-0-error-adding-symbols-dso-missing-from-command-line) + + +### macOS, Linux, Windows +```text +[ 99%] Linking CXX executable rv +CMakeFiles/rv.dir/main.cpp.o: In function `utf8Main(int, char**)': +/OpenRV/src/bin/apps/rv/main.cpp:419: undefined reference to `qInitResources_rv()' +collect2: error: ld returned 1 exit status +make[2]: *** [src/bin/apps/rv/rv] Error 1 +make[1]: *** [src/bin/apps/rv/CMakeFiles/rv.dir/all] Error 2 +make: *** [all] Error 2 +``` + +**Solution:** + +Most likely missing a `qt5_add_resources` CMake statement. +Ref.: https://forum.qt.io/topic/88959/undefined-reference-to-qinitresources/2 diff --git a/doc/build_system/config_linux_centos7.md b/doc/build_system/config_linux_centos7.md new file mode 100644 index 000000000..383e51c6c --- /dev/null +++ b/doc/build_system/config_linux_centos7.md @@ -0,0 +1,112 @@ +# Building Open RV on CentOS 7 + +## Summary + +1. [Install Basics](#install-basics) +1. [Install devtoolset-9](#install-devtoolset-9) +1. [Install tools and build dependencies](#install-tools-and-build-dependencies) +1. [Install CMake](#install-cmake) +1. [Install nasm](#install-nasm) +1. [Install Qt5](#install-qt) +1. [Configure CMake](#configure) +1. [Build Open RV](#build-including-incremental-builds) + +## Install Basics + +Make sure we have some basic tools available on the workstation: + +```bash +sudo yum install sudo wget git +``` + +## Install devtoolset-9 + +By default the CentOS 7.9 built-in tools won't match the requirements we have to build RV. Install [devtoolset-9](https://www.softwarecollections.org/en/scls/rhscl/devtoolset-9/] to remedy the situation. + +```bash +sudo yum install centos-release-scl +sudo yum-config-manager --enable rhel-server-rhscl-7-rpms +sudo yum install devtoolset-9 + +# Note that you have to activate this all of the time (or add it to shell user initialization (e.g.: .bashrc)) +scl enable devtoolset-9 $SHELL +``` + +## Install tools and build dependencies + +Most of the build requirements can be installed using the following command: + +```bash +sudo yum install alsa-lib-devel autoconf automake avahi-compat-libdns_sd-devel bison bzip2-devel cmake-gui curl-devel flex glew-devel libXcomposite libXi-devel libaio-devel libffi-devel libncurses-devel libtool libxkbcommon openssl-devel pulseaudio-libs pulseaudio-libs-glib2 ocl-icd opencl-headers python3 python3-devel qt5-qtbase-devel readline-devel sqlite-devel tcl-devel tk-devel yasm zlib-devel +``` + +### Install the python requirements + +Some of the RV build scripts requires extra python packages. They can be installed using the requirements.txt at the root of the repository. + +```bash +python3 -m pip install -r requirements.txt +``` + +## Install CMake + +You need CMake version 3.24+ to build RV. The yum-installable version is not quite recent enough, you'll to build and install CMake from sources. + +```bash +wget https://github.com/Kitware/CMake/releases/download/v3.24.0/cmake-3.24.0.tar.gz +tar -zxvf cmake-3.24.0.tar.gz +cd cmake-3.24.0 +./bootstrap --parallel=32 # 32 or whatever your machine allows +make -j 32 # 32 or whatever your machine allows +sudo make install + +cmake --version # confirm the version of you're newly installed version of CMake +cmake version 3.24.0 +``` + +## Install nasm + +The `nasm` tool is required to build the `libdav1d` and `ffmpeg` dependencies. +The yum-provided version is slightly too old and you'll required building from sources. +Fortunately, building `nasm` from source is as easy as it gets: + +```bash +wget https://www.nasm.us/pub/nasm/releasebuilds/2.15.05/nasm-2.15.05.tar.gz +tar xf nasm-2.15.05.tar.gz +cd nasm-2.15.05 +scl enable devtoolset-9 "$SHELL -c './configure'" +scl enable devtoolset-9 "$SHELL -c 'make -j'" +sudo make install +``` + +## Install Qt + +A full distribution of Qt 5.15 is required to build RV. You can download Qt 5.15.2 from the [Qt page](https://www.qt.io/download) (an account might be required). + +From the online installer, select everything under 5.15.2 except Logs, and Android. + +## Configure + +The project uses CMake and requires a configure step before building. It is during the configure step that you provide your Qt package. + +From the root of the repository, execute `cmake` specifying the path to an arbitrary build folder and the path to your QT5 package. + +For example: + +```bash +cmake -B cmake-build -DRV_DEPS_QT5_LOCATION=/Path/To/Your/Qt5/Rootr +``` + +### Custom generator + +You can decide to build with another generator. Ninja is known to work well with the build. If you desire to build with Ninja, you can set`-G Ninja` + +## Build (including incremental builds) + +Invoke the build tool using cmake (recommended). + +For example: + +```bash +cmake --build cmake-build --target rv +``` diff --git a/doc/build_system/config_macos.md b/doc/build_system/config_macos.md new file mode 100644 index 000000000..c752e001c --- /dev/null +++ b/doc/build_system/config_macos.md @@ -0,0 +1,74 @@ +# Building Open RV on macOS + +## Summary + +1. [Install XCode](#install-xcode) +1. [Install Homebrew](#install-homebrew) +1. [Install tools and build dependencies](#install-tools-and-build-dependencies) +1. [Install the python requirements](#install-the-python-requirements) +1. [Install Qt](#install-qt) +1. [Configure CMake](#configure) +1. [Invoke CMake](#build-including-incremental-builds) + +## Install XCode + +From the App Store, download XCode. Make sure that it's the source of the active developer directory. + +`xcode-select -p` should return `/Applications/Xcode.app/Contents/Developer`. If it's not the case, run `sudo xcode-select -s /Applications/Xcode.app` + +## Install Homebrew + +Homebrew is the one stop shop providing all the build requirements. You can install it following the instructions on the [Homebrew page](https://brew.sh). + +Make sure Homebrew's binary directory is in your PATH and that `brew` is resolved from your terminal. + +## Install tools and build dependencies + +Most of the build requirements can be installed by running the following brew install command: + +```bash +brew install cmake ninja readline sqlite3 xz zlib tcl-tk autoconf automake libtool python yasm clang-format black meson nasm pkg-config glew +``` + +Make sure `python` resolves in your terminal. In some case, depending on how the python formula is built, there's no `python` symbolic link. +In that case, you can create one with this command `ln -s python3 $(dirname $(which python3))/python`. + +## Install the python requirements + +Some of the RV build scripts requires extra python packages. They can be installed using the requirements.txt at the root of the repository. + +```bash +python3 -m pip install -r requirements.txt +``` + +## Install Qt + +A full distribution of Qt 5.15 is required to build RV. You can download Qt 5.15.2 from the [Qt page](https://www.qt.io/download) (an account might be required). + +From the online installer, select everything under 5.15.2 except Logs, Android, and iOS. + +## Configure + +The project uses CMake and requires a configure step before building. It is during the configure step that you provide your Qt package. + +From the root of the repository, execute `cmake` specifying the path to an arbitrary build folder and the path to your QT5 package. + +For example: + +```bash +cmake -B cmake-build -DCMAKE_BUILD_TYPE=Release -DRV_DEPS_QT5_LOCATION=$HOME/Qt5.15.11/5.15.11/clang_64 +``` + +### Custom generator + +You can decide to build with another generator. Ninja is known to work well with the build. If you desire to build with Ninja, you can set`-G Ninja` + +## Build (including incremental builds) + +Invoke the build tool using cmake (recommended). + +For example + +```bash +cmake --build cmake-build --target RV +``` diff --git a/doc/build_system/config_windows.md b/doc/build_system/config_windows.md new file mode 100644 index 000000000..183322c6f --- /dev/null +++ b/doc/build_system/config_windows.md @@ -0,0 +1,118 @@ +# Building Open RV on Windows + +## Summary + +1. [Install Microsoft Visual Studio](#1-install-microsoft-visual-studio) +1. [Install Qt](#2-install-qt) +1. [Install Strawberry Perl](#3-install-strawberry-perl) +1. [Install MSYS2](#4-install-msys2) +1. [Install required MSYS2 pacman packages (from an MSYS2-MinGW64 shell)](#5-install-required-msys2-pacman-packages) +1. [Install the Python requirements (from an MSYS2-MinGW64 shell)](#6-install-the-python-requirements) +1. [Clone the RV repo (from an MSYS2-MinGW64 shell)](#7-clone-the-rv-repo) +1. [Invoke CMake](#8-invoke-cmake) + +## 1. Install Microsoft Visual Studio + +Install Microsoft Visual Studio 2017 & 2019. + +Any edition of Microsoft Visual Studio 2019 should do, even the Microsoft Visual Studio 2019 Community Edition. Download it from the Microsoft [older downloads page](https://visualstudio.microsoft.com/vs/older-downloads/). + +Make sure to select the "Desktop development with C++" and the latest SDK for Windows 10 or Windows 11 features when installing Microsoft Visual Studio. + +You select the Windows SDK based on the target Windows version you plan on running the compiled application on. + +Note: The current version of PySide2 required by RV (5.15.2.1) cannot be built with Microsoft Visual Studio 2019 : it can only be built with Microsoft Visual Studio 2017. Therefore, Microsoft Visual Studio 2017 needs to be installed as well. + +## 2. Install Qt + +Download the last version of Qt 5.15.x that you can get using the online installer on the [Qt page](https://account.qt.io/s/downloads) (an account might be required). + +Note: If you do have jom installed you will need it to build. You can download it from here: https://download.qt.io/official_releases/jom/. The contents of the package need to be copied into the QT installation directory under Tools/QtCreator/bin/jom. + +## 3. Install Strawberry Perl + +Download and install the 64-bit version of [Strawberry Perl](https://strawberryperl.com/) + +## 4. Install MSYS2 + +Download and install the latest [MSYS2](https://www.msys2.org/). + +Note that RV is NOT a mingw64 build. It is a Microscoft Visual Studio 2019 build. + +RV is built with Microsoft Visual Studio 2019 via the cmake "Visual Studio 16 2019" generator. + +msys2 is only used for convenience as it comes with a package manager with utility packages required for the RV build such as cmake, git, flex, bison, nasm, unzip, zip, etc. + +NOTE: The Windows' WSL2 feature conflict with MSYS2. For simplicity, it is recommanded to disable Windows' WSL or WSL2 feature entirely. + +Additional information can be found on the [MSYS2 github](https://github.com/msys2/setup-msys2/issues/52). + +## 5. Install required MSYS2 pacman packages + +From an MSYS2-MinGW64 shell, install the following packages which are required to build RV: + +```shell +pacman -S --needed \ + mingw-w64-x86_64-autotools \ + mingw-w64-x86_64-cmake \ + mingw-w64-x86_64-glew \ + mingw-w64-x86_64-libarchive \ + mingw-w64-x86_64-make \ + mingw-w64-x86_64-meson \ + mingw-w64-x86_64-python-pip \ + mingw-w64-x86_64-python-psutil \ + mingw-w64-x86_64-toolchain \ + bison \ + flex \ + git \ + nasm \ + p7zip \ + unzip \ + zip +``` + +## 6. Install the Python requirements + +From an MSYS2-MinGW64 shell, install the required python dependencies using: + +```shell +python3 -m pip install --user -r requirements.txt +``` + +Note: Enter the following command from the same MSYS2-MinGW64 shell if you encounter an error when installing the py7zr python requirement as it seems to be an issue with the latest version of MSYS2: + +```shell +SETUPTOOLS_USE_DISTUTILS=stdlib pip install py7zr +``` + +Note that to successfully build Open RV in debug on Windows, you must also install python3 ([download page](https://www.python.org/downloads/)) as it is required to build the opentimelineio python wheel in debug. + +This step is not required if you do not intend to build the debug version of RV. + +## 7. Clone the RV repo + +From an MSYS2-MinGW64 shell: + +```shell +cd /c +git clone git@github.com:AcademySoftwareFoundation/OpenRV.git + +OR + +git clone https://github.com/AcademySoftwareFoundation/OpenRV.git +``` + +NOTE: Even as of Windows 11, for legacy reason, a default system path length is still limited to 254 bytes long. +For that reason we strongly suggest cloning `OpenRV` into drive's root directory e.g.: `C:\` + +## 8. Invoke CMake + +From an MSYS2-MinGW64 shell: + +```shell +cmake -B cmake-build -G "Visual Studio 16 2019" -A x64 -DCMAKE_BUILD_TYPE=Release -DRV_DEPS_WIN_PERL_ROOT=/c/Strawberry/perl/bin -DRV_DEPS_QT5_LOCATION=/c/Qt/Qt5.15.11/5.15.11/msvc2019_64 +cmake --build cmake-build --config Release --target rv -j8 +cmake --install cmake-build --config Release +``` + +NOTE: It is recommanded to use Unix-like syntax with the`RV_DEPS_QT5_LOCATION` defininition, otherwise one might use the Windows-like syntax such as `C:\\QT\\5.15.11...` note of the escaping of the backslash characters. diff --git a/doc/build_system/win_how_to_enable_dot_NET_3_5.png b/doc/build_system/win_how_to_enable_dot_NET_3_5.png new file mode 100644 index 000000000..9241d7455 Binary files /dev/null and b/doc/build_system/win_how_to_enable_dot_NET_3_5.png differ diff --git a/doc/build_system/win_microsoft_visual_studio_features_to_install.png b/doc/build_system/win_microsoft_visual_studio_features_to_install.png new file mode 100644 index 000000000..960bff120 Binary files /dev/null and b/doc/build_system/win_microsoft_visual_studio_features_to_install.png differ diff --git a/doc/images/rv-hotkeys-describe-options-03.png b/doc/images/rv-hotkeys-describe-options-03.png new file mode 100644 index 000000000..d819fa16f Binary files /dev/null and b/doc/images/rv-hotkeys-describe-options-03.png differ diff --git a/doc/images/rv-hotkeys-describe-options-rv-02.png b/doc/images/rv-hotkeys-describe-options-rv-02.png new file mode 100644 index 000000000..29febee9b Binary files /dev/null and b/doc/images/rv-hotkeys-describe-options-rv-02.png differ diff --git a/doc/images/rv-hotkeys-help-menu-01.png b/doc/images/rv-hotkeys-help-menu-01.png new file mode 100644 index 000000000..28fe19b69 Binary files /dev/null and b/doc/images/rv-hotkeys-help-menu-01.png differ diff --git a/doc/images/rv-hotkeys-hotkeys-04.png b/doc/images/rv-hotkeys-hotkeys-04.png new file mode 100644 index 000000000..452367a87 Binary files /dev/null and b/doc/images/rv-hotkeys-hotkeys-04.png differ diff --git a/doc/images/rv-luts-lut-pipeline-diagram-01.png b/doc/images/rv-luts-lut-pipeline-diagram-01.png new file mode 100644 index 000000000..42aa21224 Binary files /dev/null and b/doc/images/rv-luts-lut-pipeline-diagram-01.png differ diff --git a/doc/images/rv-manuals-media-multi-representation-dropdown-menu.png b/doc/images/rv-manuals-media-multi-representation-dropdown-menu.png new file mode 100644 index 000000000..7962ffe9a Binary files /dev/null and b/doc/images/rv-manuals-media-multi-representation-dropdown-menu.png differ diff --git a/doc/images/rv-manuals-media-multi-representation-media-info.png b/doc/images/rv-manuals-media-multi-representation-media-info.png new file mode 100644 index 000000000..0f4f6231d Binary files /dev/null and b/doc/images/rv-manuals-media-multi-representation-media-info.png differ diff --git a/doc/images/rv-manuals-media-multi-representation-scheme-no-switch-group.png b/doc/images/rv-manuals-media-multi-representation-scheme-no-switch-group.png new file mode 100644 index 000000000..ef51d459b Binary files /dev/null and b/doc/images/rv-manuals-media-multi-representation-scheme-no-switch-group.png differ diff --git a/doc/images/rv-manuals-media-multi-representation-scheme-with-switch-group.png b/doc/images/rv-manuals-media-multi-representation-scheme-with-switch-group.png new file mode 100644 index 000000000..c9cd0d34a Binary files /dev/null and b/doc/images/rv-manuals-media-multi-representation-scheme-with-switch-group.png differ diff --git a/doc/images/rv-manuals-media-multi-representation-swap-menu.png b/doc/images/rv-manuals-media-multi-representation-swap-menu.png new file mode 100644 index 000000000..18a8fa51d Binary files /dev/null and b/doc/images/rv-manuals-media-multi-representation-swap-menu.png differ diff --git a/doc/images/rv-reference-manual-10-rv-cx-rv4-prop-active-09.png b/doc/images/rv-reference-manual-10-rv-cx-rv4-prop-active-09.png new file mode 100644 index 000000000..73d049944 Binary files /dev/null and b/doc/images/rv-reference-manual-10-rv-cx-rv4-prop-active-09.png differ diff --git a/doc/images/rv-reference-manual-11-rv-cx-ase-overlay-shot-010.png b/doc/images/rv-reference-manual-11-rv-cx-ase-overlay-shot-010.png new file mode 100644 index 000000000..55a516e69 Binary files /dev/null and b/doc/images/rv-reference-manual-11-rv-cx-ase-overlay-shot-010.png differ diff --git a/doc/images/rv-reference-manual-12-rv-cx-overlay-example-11.png b/doc/images/rv-reference-manual-12-rv-cx-overlay-example-11.png new file mode 100644 index 000000000..a66260876 Binary files /dev/null and b/doc/images/rv-reference-manual-12-rv-cx-overlay-example-11.png differ diff --git a/doc/images/rv-reference-manual-13-rv-cx-lease-event-prop-12.png b/doc/images/rv-reference-manual-13-rv-cx-lease-event-prop-12.png new file mode 100644 index 000000000..88f6a7cf4 Binary files /dev/null and b/doc/images/rv-reference-manual-13-rv-cx-lease-event-prop-12.png differ diff --git a/doc/images/rv-reference-manual-2-rv-cxx-se-rv4-top-level-01.png b/doc/images/rv-reference-manual-2-rv-cxx-se-rv4-top-level-01.png new file mode 100644 index 000000000..820bc3db5 Binary files /dev/null and b/doc/images/rv-reference-manual-2-rv-cxx-se-rv4-top-level-01.png differ diff --git a/doc/images/rv-reference-manual-3-rv-cxx-rv4-source-group-02.png b/doc/images/rv-reference-manual-3-rv-cxx-rv4-source-group-02.png new file mode 100644 index 000000000..e23d4865f Binary files /dev/null and b/doc/images/rv-reference-manual-3-rv-cxx-rv4-source-group-02.png differ diff --git a/doc/images/rv-reference-manual-4-rv-cxx-e-rv4-view-group-03.png b/doc/images/rv-reference-manual-4-rv-cxx-e-rv4-view-group-03.png new file mode 100644 index 000000000..a0c60d226 Binary files /dev/null and b/doc/images/rv-reference-manual-4-rv-cxx-e-rv4-view-group-03.png differ diff --git a/doc/images/rv-reference-manual-5-rv-cxx-rv4-sequence-group-04.png b/doc/images/rv-reference-manual-5-rv-cxx-rv4-sequence-group-04.png new file mode 100644 index 000000000..27859bc50 Binary files /dev/null and b/doc/images/rv-reference-manual-5-rv-cxx-rv4-sequence-group-04.png differ diff --git a/doc/images/rv-reference-manual-6-rv-cxx-rv4-stack-group-05.png b/doc/images/rv-reference-manual-6-rv-cxx-rv4-stack-group-05.png new file mode 100644 index 000000000..cbea2ca71 Binary files /dev/null and b/doc/images/rv-reference-manual-6-rv-cxx-rv4-stack-group-05.png differ diff --git a/doc/images/rv-reference-manual-7-rv-cxx-rv4-layout-group-06.png b/doc/images/rv-reference-manual-7-rv-cxx-rv4-layout-group-06.png new file mode 100644 index 000000000..3c90fe438 Binary files /dev/null and b/doc/images/rv-reference-manual-7-rv-cxx-rv4-layout-group-06.png differ diff --git a/doc/images/rv-reference-manual-8-rv-cxx-rv4-display-group-07.png b/doc/images/rv-reference-manual-8-rv-cxx-rv4-display-group-07.png new file mode 100644 index 000000000..153b01c48 Binary files /dev/null and b/doc/images/rv-reference-manual-8-rv-cxx-rv4-display-group-07.png differ diff --git a/doc/images/rv-reference-manual-9-rv-cxx-rv4-prop-inactive-08.png b/doc/images/rv-reference-manual-9-rv-cxx-rv4-prop-inactive-08.png new file mode 100644 index 000000000..ecadbcf38 Binary files /dev/null and b/doc/images/rv-reference-manual-9-rv-cxx-rv4-prop-inactive-08.png differ diff --git a/doc/images/rv-user-manual-11-rv-cxx98-release-inspector-010.jpg b/doc/images/rv-user-manual-11-rv-cxx98-release-inspector-010.jpg new file mode 100644 index 000000000..22c259f24 Binary files /dev/null and b/doc/images/rv-user-manual-11-rv-cxx98-release-inspector-010.jpg differ diff --git a/doc/images/rv-user-manual-12-rv-cxx98-release-average-11.jpg b/doc/images/rv-user-manual-12-rv-cxx98-release-average-11.jpg new file mode 100644 index 000000000..c7cfef0d0 Binary files /dev/null and b/doc/images/rv-user-manual-12-rv-cxx98-release-average-11.jpg differ diff --git a/doc/images/rv-user-manual-14-rv-cx-e-nearest-filter-13.jpg b/doc/images/rv-user-manual-14-rv-cx-e-nearest-filter-13.jpg new file mode 100644 index 000000000..5061a3820 Binary files /dev/null and b/doc/images/rv-user-manual-14-rv-cx-e-nearest-filter-13.jpg differ diff --git a/doc/images/rv-user-manual-15-rv-cx-se-linear-filter-14.jpg b/doc/images/rv-user-manual-15-rv-cx-se-linear-filter-14.jpg new file mode 100644 index 000000000..98292ca70 Binary files /dev/null and b/doc/images/rv-user-manual-15-rv-cx-se-linear-filter-14.jpg differ diff --git a/doc/images/rv-user-manual-16-rv-cx-ase-float-linear-15.jpg b/doc/images/rv-user-manual-16-rv-cx-ase-float-linear-15.jpg new file mode 100644 index 000000000..7350f0bbc Binary files /dev/null and b/doc/images/rv-user-manual-16-rv-cx-ase-float-linear-15.jpg differ diff --git a/doc/images/rv-user-manual-17-rv-cx-ease-8bit-linear-16.jpg b/doc/images/rv-user-manual-17-rv-cx-ease-8bit-linear-16.jpg new file mode 100644 index 000000000..9830253fe Binary files /dev/null and b/doc/images/rv-user-manual-17-rv-cx-ease-8bit-linear-16.jpg differ diff --git a/doc/images/rv-user-manual-18-rv-cx-ase-8bit-nearest-17.jpg b/doc/images/rv-user-manual-18-rv-cx-ase-8bit-nearest-17.jpg new file mode 100644 index 000000000..921f70845 Binary files /dev/null and b/doc/images/rv-user-manual-18-rv-cx-ase-8bit-nearest-17.jpg differ diff --git a/doc/images/rv-user-manual-19-rv-cx-e-infoWidgetShot-18.png b/doc/images/rv-user-manual-19-rv-cx-e-infoWidgetShot-18.png new file mode 100644 index 000000000..7f1984a2d Binary files /dev/null and b/doc/images/rv-user-manual-19-rv-cx-e-infoWidgetShot-18.png differ diff --git a/doc/images/rv-user-manual-20-rv-cx-timeline-labelled-19.png b/doc/images/rv-user-manual-20-rv-cx-timeline-labelled-19.png new file mode 100644 index 000000000..0e1e38f15 Binary files /dev/null and b/doc/images/rv-user-manual-20-rv-cx-timeline-labelled-19.png differ diff --git a/doc/images/rv-user-manual-21-rv-cx-timeline-reddot-20.png b/doc/images/rv-user-manual-21-rv-cx-timeline-reddot-20.png new file mode 100644 index 000000000..e6ab82eae Binary files /dev/null and b/doc/images/rv-user-manual-21-rv-cx-timeline-reddot-20.png differ diff --git a/doc/images/rv-user-manual-22-rv-cx-timelineMenuShot-21.jpg b/doc/images/rv-user-manual-22-rv-cx-timelineMenuShot-21.jpg new file mode 100644 index 000000000..bfc15f74e Binary files /dev/null and b/doc/images/rv-user-manual-22-rv-cx-timelineMenuShot-21.jpg differ diff --git a/doc/images/rv-user-manual-23-rv-cx-timelineMarksShot-22.png b/doc/images/rv-user-manual-23-rv-cx-timelineMarksShot-22.png new file mode 100644 index 000000000..e0e2dee69 Binary files /dev/null and b/doc/images/rv-user-manual-23-rv-cx-timelineMarksShot-22.png differ diff --git a/doc/images/rv-user-manual-24-rv-cx-magnifierMenuShot-23.jpg b/doc/images/rv-user-manual-24-rv-cx-magnifierMenuShot-23.jpg new file mode 100644 index 000000000..c2e573883 Binary files /dev/null and b/doc/images/rv-user-manual-24-rv-cx-magnifierMenuShot-23.jpg differ diff --git a/doc/images/rv-user-manual-25-rv-cx-audio-prefs-mac-24.png b/doc/images/rv-user-manual-25-rv-cx-audio-prefs-mac-24.png new file mode 100644 index 000000000..d5e44679a Binary files /dev/null and b/doc/images/rv-user-manual-25-rv-cx-audio-prefs-mac-24.png differ diff --git a/doc/images/rv-user-manual-26-rv-cx-e-timeline-cache-25.png b/doc/images/rv-user-manual-26-rv-cx-e-timeline-cache-25.png new file mode 100644 index 000000000..4b126fc23 Binary files /dev/null and b/doc/images/rv-user-manual-26-rv-cx-e-timeline-cache-25.png differ diff --git a/doc/images/rv-user-manual-27-rv-cx-ase-region-cache-26.png b/doc/images/rv-user-manual-27-rv-cx-ase-region-cache-26.png new file mode 100644 index 000000000..d9530296f Binary files /dev/null and b/doc/images/rv-user-manual-27-rv-cx-ase-region-cache-26.png differ diff --git a/doc/images/rv-user-manual-28-rv-cx-region-cache-no-room-27.png b/doc/images/rv-user-manual-28-rv-cx-region-cache-no-room-27.png new file mode 100644 index 000000000..f27838d69 Binary files /dev/null and b/doc/images/rv-user-manual-28-rv-cx-region-cache-no-room-27.png differ diff --git a/doc/images/rv-user-manual-29-rv-cx-look-ahead-cache-28.png b/doc/images/rv-user-manual-29-rv-cx-look-ahead-cache-28.png new file mode 100644 index 000000000..deab39a06 Binary files /dev/null and b/doc/images/rv-user-manual-29-rv-cx-look-ahead-cache-28.png differ diff --git a/doc/images/rv-user-manual-30-rv-cx-session-manager-29.png b/doc/images/rv-user-manual-30-rv-cx-session-manager-29.png new file mode 100644 index 000000000..b2a1c2bb9 Binary files /dev/null and b/doc/images/rv-user-manual-30-rv-cx-session-manager-29.png differ diff --git a/doc/images/rv-user-manual-31-rv-cx-session-manager2-30.png b/doc/images/rv-user-manual-31-rv-cx-session-manager2-30.png new file mode 100644 index 000000000..77e44a479 Binary files /dev/null and b/doc/images/rv-user-manual-31-rv-cx-session-manager2-30.png differ diff --git a/doc/images/rv-user-manual-32-rv-cx-ease-sm-add-menu-31.png b/doc/images/rv-user-manual-32-rv-cx-ease-sm-add-menu-31.png new file mode 100644 index 000000000..ce196aa4f Binary files /dev/null and b/doc/images/rv-user-manual-32-rv-cx-ease-sm-add-menu-31.png differ diff --git a/doc/images/rv-user-manual-33-rv-cx-e-sm-folder-menu-32.png b/doc/images/rv-user-manual-33-rv-cx-e-sm-folder-menu-32.png new file mode 100644 index 000000000..7e5da8466 Binary files /dev/null and b/doc/images/rv-user-manual-33-rv-cx-e-sm-folder-menu-32.png differ diff --git a/doc/images/rv-user-manual-34-rv-cx-ase-sourceSMShot-33.png b/doc/images/rv-user-manual-34-rv-cx-ase-sourceSMShot-33.png new file mode 100644 index 000000000..1a2a8aaac Binary files /dev/null and b/doc/images/rv-user-manual-34-rv-cx-ase-sourceSMShot-33.png differ diff --git a/doc/images/rv-user-manual-37-rv-cx-e-sequenceSMShot-36.jpg b/doc/images/rv-user-manual-37-rv-cx-e-sequenceSMShot-36.jpg new file mode 100644 index 000000000..c9d62f2d7 Binary files /dev/null and b/doc/images/rv-user-manual-37-rv-cx-e-sequenceSMShot-36.jpg differ diff --git a/doc/images/rv-user-manual-38-rv-cx-ease-stackSMShot-37.jpg b/doc/images/rv-user-manual-38-rv-cx-ease-stackSMShot-37.jpg new file mode 100644 index 000000000..72ef4ce3e Binary files /dev/null and b/doc/images/rv-user-manual-38-rv-cx-ease-stackSMShot-37.jpg differ diff --git a/doc/images/rv-user-manual-39-rv-cx-ase-layoutSMShot-38.jpg b/doc/images/rv-user-manual-39-rv-cx-ase-layoutSMShot-38.jpg new file mode 100644 index 000000000..6b3816af7 Binary files /dev/null and b/doc/images/rv-user-manual-39-rv-cx-ase-layoutSMShot-38.jpg differ diff --git a/doc/images/rv-user-manual-4-rv-cxx-rv-linux-snapshot-03.jpg b/doc/images/rv-user-manual-4-rv-cxx-rv-linux-snapshot-03.jpg new file mode 100644 index 000000000..d401f95a7 Binary files /dev/null and b/doc/images/rv-user-manual-4-rv-cxx-rv-linux-snapshot-03.jpg differ diff --git a/doc/images/rv-user-manual-40-rv-cx-ase-retimeSMShot-39.jpg b/doc/images/rv-user-manual-40-rv-cx-ase-retimeSMShot-39.jpg new file mode 100644 index 000000000..d33a2cc77 Binary files /dev/null and b/doc/images/rv-user-manual-40-rv-cx-ase-retimeSMShot-39.jpg differ diff --git a/doc/images/rv-user-manual-41-rv-cxx98-release-folders-40.jpg b/doc/images/rv-user-manual-41-rv-cxx98-release-folders-40.jpg new file mode 100644 index 000000000..b1b3372e2 Binary files /dev/null and b/doc/images/rv-user-manual-41-rv-cxx98-release-folders-40.jpg differ diff --git a/doc/images/rv-user-manual-42-rv-cx-ease-video-prefs-41.png b/doc/images/rv-user-manual-42-rv-cx-ease-video-prefs-41.png new file mode 100644 index 000000000..4c7618a2f Binary files /dev/null and b/doc/images/rv-user-manual-42-rv-cx-ease-video-prefs-41.png differ diff --git a/doc/images/rv-user-manual-43-rv-cx-e-profile-dialog-42.png b/doc/images/rv-user-manual-43-rv-cx-e-profile-dialog-42.png new file mode 100644 index 000000000..6f7db5fef Binary files /dev/null and b/doc/images/rv-user-manual-43-rv-cx-e-profile-dialog-42.png differ diff --git a/doc/images/rv-user-manual-44-rv-cx-ase-macCustomRes-43.jpg b/doc/images/rv-user-manual-44-rv-cx-ase-macCustomRes-43.jpg new file mode 100644 index 000000000..d9662fa5a Binary files /dev/null and b/doc/images/rv-user-manual-44-rv-cx-ase-macCustomRes-43.jpg differ diff --git a/doc/images/rv-user-manual-45-rv-cx-ase-winCustomRes-44.jpg b/doc/images/rv-user-manual-45-rv-cx-ase-winCustomRes-44.jpg new file mode 100644 index 000000000..06a3a9b2d Binary files /dev/null and b/doc/images/rv-user-manual-45-rv-cx-ase-winCustomRes-44.jpg differ diff --git a/doc/images/rv-user-manual-46-rv-cx-pipeline-diagram-4-0-45.png b/doc/images/rv-user-manual-46-rv-cx-pipeline-diagram-4-0-45.png new file mode 100644 index 000000000..0f08b1718 Binary files /dev/null and b/doc/images/rv-user-manual-46-rv-cx-pipeline-diagram-4-0-45.png differ diff --git a/doc/images/rv-user-manual-47-rv-cxx98-release-crop-46.png b/doc/images/rv-user-manual-47-rv-cxx98-release-crop-46.png new file mode 100644 index 000000000..9800b3983 Binary files /dev/null and b/doc/images/rv-user-manual-47-rv-cxx98-release-crop-46.png differ diff --git a/doc/images/rv-user-manual-48-rv-cx-ase-uncrop-basic-47.png b/doc/images/rv-user-manual-48-rv-cx-ase-uncrop-basic-47.png new file mode 100644 index 000000000..c71a2c554 Binary files /dev/null and b/doc/images/rv-user-manual-48-rv-cx-ase-uncrop-basic-47.png differ diff --git a/doc/images/rv-user-manual-49-rv-cx-uncrop-over-render-48.png b/doc/images/rv-user-manual-49-rv-cx-uncrop-over-render-48.png new file mode 100644 index 000000000..433784f9e Binary files /dev/null and b/doc/images/rv-user-manual-49-rv-cx-uncrop-over-render-48.png differ diff --git a/doc/images/rv-user-manual-5-rv-cxx-rv-osx-snapshot-04.jpg b/doc/images/rv-user-manual-5-rv-cxx-rv-osx-snapshot-04.jpg new file mode 100644 index 000000000..ddd124066 Binary files /dev/null and b/doc/images/rv-user-manual-5-rv-cxx-rv-osx-snapshot-04.jpg differ diff --git a/doc/images/rv-user-manual-50-rv-cx-lut-pipeline-diagram-49.png b/doc/images/rv-user-manual-50-rv-cx-lut-pipeline-diagram-49.png new file mode 100644 index 000000000..ce06aec06 Binary files /dev/null and b/doc/images/rv-user-manual-50-rv-cx-lut-pipeline-diagram-49.png differ diff --git a/doc/images/rv-user-manual-51-rv-cx-ase-packagesShot-50.jpg b/doc/images/rv-user-manual-51-rv-cx-ase-packagesShot-50.jpg new file mode 100644 index 000000000..2092833b9 Binary files /dev/null and b/doc/images/rv-user-manual-51-rv-cx-ase-packagesShot-50.jpg differ diff --git a/doc/images/rv-user-manual-52-rv-cx-se-dice-anaglyph-51.png b/doc/images/rv-user-manual-52-rv-cx-se-dice-anaglyph-51.png new file mode 100644 index 000000000..3190a740f Binary files /dev/null and b/doc/images/rv-user-manual-52-rv-cx-se-dice-anaglyph-51.png differ diff --git a/doc/images/rv-user-manual-53-rv-cx-dice-luma-anaglyph-52.png b/doc/images/rv-user-manual-53-rv-cx-dice-luma-anaglyph-52.png new file mode 100644 index 000000000..d2d51fd8e Binary files /dev/null and b/doc/images/rv-user-manual-53-rv-cx-dice-luma-anaglyph-52.png differ diff --git a/doc/images/rv-user-manual-54-rv-cx-dice-desat-anaglyph-53.png b/doc/images/rv-user-manual-54-rv-cx-dice-desat-anaglyph-53.png new file mode 100644 index 000000000..a013734f7 Binary files /dev/null and b/doc/images/rv-user-manual-54-rv-cx-dice-desat-anaglyph-53.png differ diff --git a/doc/images/rv-user-manual-55-rv-cx-dice-side-by-side-55.png b/doc/images/rv-user-manual-55-rv-cx-dice-side-by-side-55.png new file mode 100644 index 000000000..6cefe4443 Binary files /dev/null and b/doc/images/rv-user-manual-55-rv-cx-dice-side-by-side-55.png differ diff --git a/doc/images/rv-user-manual-56-rv-cx-ease-dice-mirror-56.png b/doc/images/rv-user-manual-56-rv-cx-ease-dice-mirror-56.png new file mode 100644 index 000000000..9645a09c0 Binary files /dev/null and b/doc/images/rv-user-manual-56-rv-cx-ease-dice-mirror-56.png differ diff --git a/doc/images/rv-user-manual-57-rv-dice-dlp-and-scanline-57.png b/doc/images/rv-user-manual-57-rv-dice-dlp-and-scanline-57.png new file mode 100644 index 000000000..e739c9321 Binary files /dev/null and b/doc/images/rv-user-manual-57-rv-dice-dlp-and-scanline-57.png differ diff --git a/doc/images/rv-user-manual-58-rv-cx-dice-offset-change-58.png b/doc/images/rv-user-manual-58-rv-cx-dice-offset-change-58.png new file mode 100644 index 000000000..404c722ad Binary files /dev/null and b/doc/images/rv-user-manual-58-rv-cx-dice-offset-change-58.png differ diff --git a/doc/images/rv-user-manual-59-rv-cx-dice-one-eye-flipped-59.png b/doc/images/rv-user-manual-59-rv-cx-dice-one-eye-flipped-59.png new file mode 100644 index 000000000..8b1dc9839 Binary files /dev/null and b/doc/images/rv-user-manual-59-rv-cx-dice-one-eye-flipped-59.png differ diff --git a/doc/images/rv-user-manual-6-rv-cxx98-release-feedback-05.jpg b/doc/images/rv-user-manual-6-rv-cxx98-release-feedback-05.jpg new file mode 100644 index 000000000..e94afdfac Binary files /dev/null and b/doc/images/rv-user-manual-6-rv-cxx98-release-feedback-05.jpg differ diff --git a/doc/images/rv-user-manual-60-rv-cx-se-rv-networking-60.jpg b/doc/images/rv-user-manual-60-rv-cx-se-rv-networking-60.jpg new file mode 100644 index 000000000..bd4f1b1ea Binary files /dev/null and b/doc/images/rv-user-manual-60-rv-cx-se-rv-networking-60.jpg differ diff --git a/doc/images/rv-user-manual-61-rv-cx-network-connect-61.jpg b/doc/images/rv-user-manual-61-rv-cx-network-connect-61.jpg new file mode 100644 index 000000000..70abfef11 Binary files /dev/null and b/doc/images/rv-user-manual-61-rv-cx-network-connect-61.jpg differ diff --git a/doc/images/rv-user-manual-62-rv-cx-network-contacts-62.jpg b/doc/images/rv-user-manual-62-rv-cx-network-contacts-62.jpg new file mode 100644 index 000000000..5ed0c0ce6 Binary files /dev/null and b/doc/images/rv-user-manual-62-rv-cx-network-contacts-62.jpg differ diff --git a/doc/images/rv-user-manual-63-rv-cxx98-release-grabSync-63.png b/doc/images/rv-user-manual-63-rv-cxx98-release-grabSync-63.png new file mode 100644 index 000000000..a7d5ce370 Binary files /dev/null and b/doc/images/rv-user-manual-63-rv-cxx98-release-grabSync-63.png differ diff --git a/doc/images/rv-user-manual-64-rv-cx-ase-grabSyncMenu-64.png b/doc/images/rv-user-manual-64-rv-cx-ase-grabSyncMenu-64.png new file mode 100644 index 000000000..ea05893c1 Binary files /dev/null and b/doc/images/rv-user-manual-64-rv-cx-ase-grabSyncMenu-64.png differ diff --git a/doc/images/rv-user-manual-65-rv-cx-ase-render-prefs-65.jpg b/doc/images/rv-user-manual-65-rv-cx-ase-render-prefs-65.jpg new file mode 100644 index 000000000..76a6bc148 Binary files /dev/null and b/doc/images/rv-user-manual-65-rv-cx-ase-render-prefs-65.jpg differ diff --git a/doc/images/rv-user-manual-66-rv-cx-se-caching-prefs-66.jpg b/doc/images/rv-user-manual-66-rv-cx-se-caching-prefs-66.jpg new file mode 100644 index 000000000..8998554c4 Binary files /dev/null and b/doc/images/rv-user-manual-66-rv-cx-se-caching-prefs-66.jpg differ diff --git a/doc/images/rv-user-manual-7-rv-cxx-toolbars-legend-06.png b/doc/images/rv-user-manual-7-rv-cxx-toolbars-legend-06.png new file mode 100644 index 000000000..3d94ae771 Binary files /dev/null and b/doc/images/rv-user-manual-7-rv-cxx-toolbars-legend-06.png differ diff --git a/doc/images/rv-user-manual-8-rv-cxx98-release-grabFile-07.png b/doc/images/rv-user-manual-8-rv-cxx98-release-grabFile-07.png new file mode 100644 index 000000000..4acbb6b93 Binary files /dev/null and b/doc/images/rv-user-manual-8-rv-cxx98-release-grabFile-07.png differ diff --git a/doc/images/rv-user-manual-dice-lumanaglyph-54.png b/doc/images/rv-user-manual-dice-lumanaglyph-54.png new file mode 100644 index 000000000..e8de8b9ae Binary files /dev/null and b/doc/images/rv-user-manual-dice-lumanaglyph-54.png differ diff --git a/doc/images/rv-versioning-utils-blobid0-01.jpg b/doc/images/rv-versioning-utils-blobid0-01.jpg new file mode 100644 index 000000000..0d5ad1a05 Binary files /dev/null and b/doc/images/rv-versioning-utils-blobid0-01.jpg differ diff --git a/doc/rv-manuals.md b/doc/rv-manuals.md new file mode 100644 index 000000000..a2ea7033f --- /dev/null +++ b/doc/rv-manuals.md @@ -0,0 +1,9 @@ +# Open RV Manuals + +* [Open RV User Manual](rv-manuals/rv-user-manual.md) +* [Open RV Reference Manual](rv-manuals/rv-reference-manual.md) +* [GTO: The Kitchen Sink of Data](rv-manuals/rv-gto.md) +* [Using LUTs in Open RV](rv-manuals/rv-luts.md) +* [Using Multiple Media Representations in Open RV](rv-manuals/rv-media-multi-representation.md) +* [Mu Programming Language](rv-manuals/rv-mu-programming.md) +* [OpenColorIO Development Integration Notes](rv-manuals/rv-opencolorio-integrations.md) diff --git a/doc/rv-manuals/rv-gto.md b/doc/rv-manuals/rv-gto.md new file mode 100644 index 000000000..b6ebb58eb --- /dev/null +++ b/doc/rv-manuals/rv-gto.md @@ -0,0 +1,1569 @@ +# GTO: The Kitchen Sink of Data + +File Format, Protocols, and Utilities. + +### Overview + +Historically, GTO format’s primary usage is storage of static geometric data (cached geometry). As such, the types of data you might find in a GTO file are things like polygonal meshes, various types of subdivision surfaces, NURBS or UBS surfaces, coordinate systems, hierarchies of objects, material bindings, and even images. + +From a historic point of view, the GTO file format is most closely related to the original inventor file format, the Stanford PLY format and the PDB particle format. Like the Wavefont PDB file format, there are a limited number of simple GTO data types (float, int, string, boolean). Like the inventor file format, a GTO can hold an entire transformation hierarchy including geometric leaf nodes. Like the PLY format, the GTO format can contain an arbitrary amount of data per primitive. Most importantly however, the GTO file format is intended to be very OBJ-like; its relatively easy to read and write and easy to ignore data you don’t want or know about. + +GTO files can be either binary or text files. Binary files are the preferred format for large data sets. The GTO text format is intended to be human readable/editable; the syntax is simple and concise. The text format is useful when storing “bag of parameters” files and similar data. + +The binary file is either big or little endian on disk, but should be readable on any platform. + +The GTO reader can be use libz to read and write compressed files natively. We find that compressed GTO files created by most 3D programs are approximately 60% leaner than uncompressed files. + +GTO files conceptually contain objects which are optionally composed of nested namespaces called components. Components are further composed of properties. A property contains an array of one of the predefined data types with up to a four dimensional “shape”. For example, you might have an object which looks something like this: + +``` + Object “cube” + Component “points” + Property float [3][8] “position” Property float[1][8] “mass” + Property byte[1][8] “type” Property short[1][8] “size” + Component “indices” + Property int[1][32] “vertex” +``` + +Using the terminology above, the object “cube” contains five properties: **position** , **mass** , **type** , **size** , and **vertex** . The **points** component describes the points that make up the cube vertices. Each point has a position and mass stored in properties of the same name. The position property data is composed of eight float [3] data items (or 8 3D points). The **mass** property is composed of a 8 scalar floating point values (one for each point). + +The **elements** component contains two properties. **type** indicates the type of the element (for example, triangle, quad, or triangle strip). In this case the elements might all be quads. **size** indicates the number of **vertices** in each of the eight faces (elements) of the cube—(4 for a cube). The vertex property of the **indices** component contains the actual indices: 4 per face for a total of 32. + +Of course you could store much more data with the cube object if you wanted to. For example, if you wanted velocity or color per point, this would be another property in the points component. + +The meaning of this data is another story altogether. Its all handled by protocol. One application may store things in the GTO file that another application has no method of interpreting even though it can read that data and modify it. In the example above, you need to know to expect that polygonal data is stored in the given properties. The same data could be stored with different property names and a more complex layout. (The “polygon” protocol described later in this document is different and more involved than the above example.) + +GTO was (and still is) used by a number of film post production facilities for geometry caching. 3D scenes are evaluated and the final geometry is written into GTO files which are later consumed by a renderer (e.g. RenderMan). + +A newer geometry caching format called Alembic was introduced by ILM in 2010 which has a similar purpose and has taken over that role. + +### New in Version 4 + + +Version 4 adds two new features: nested components and property types with up to four dimensions. + +Version 3 files always had the same structure of objects.component.property. Nested components allows any number of component names between the object and property: + +``` +object.component1.component2.component3.property +``` + +In text GTO files this looks like this: + +``` + object + { + component1 + { + component2 + { + component3 + { + property ... + } + } + } + } +``` + +Where version 3 allowed only a single “width” for properties, version 4 allows up to four dimensions: + +``` + float[4,10,20,30][3] = [ ... ] +``` + +The above declares 3 4x10x20x30 float data object. + +## Overview + + +## Binary Format + + +The GTO file has six major sections which appear in the following order. + +1. **Header** (Gto::Header). The header structure contains the GTO magic number (used to determine endianness), the version of the GTO specification that the file was written as, and the number of top level objects in the file. There is one instance of a header in the file. Finally, the header indicates how many strings are in the string table. + +``` + Magic = 0x0000029f; Cigam = 0x9f020000; // means the file is opposite +endianess + +struct Header + +{ + uint32 magic; + uint32 numStrings; + uint32 numObjects; + uint32 version; + uint32 flags; // reserved; +}; +``` + +2. **String Table** . After the header, null terminated strings are written in the file. The order of these strings is important. All names and string properties store indices into the string table instead of actual strings. In order to read the file properly, the string table must be available until the file is completely read. (Unless you don’t care about any strings!) + + The index number refers the string number in the table not its byte offset. So the string index 9 (for example) refers to the 10th string in the table (string index 0 is the first string in the table). + +3. **ObjectHeader** (Gto::ObjectHeader). The object header indicates what kind of protocol to use to interpret it, the **object** name and the number of components. (More on the object protocol later). The name—like all strings in the GTO file—is stored as a string table entry. If the file header indicated N objects in the file, there will be N ObjectHeaders. + + +``` +struct ObjectHeader +{ +uint32 name; // a string table index +uint32 protocolName; // a string table index +uint32 protocolVersion; +uint32 numComponents; +uint32 pad; // unused +}; +``` + +4. **ComponentHeader** (Gto::ComponentHeader). Like the ObjectHeaders the ComponentHeaders will appear together for all objects in order. The component header indicates the number of properties in the component and the name of the component. + +``` +enum ComponentFlags +{ +Transposed = 1 << 0, +Matrix = 1 << 1, +}; +struct ComponentHeader +{ +uint32 name; // a string table index +uint32 numProperties; +uint32 flags; +uint32 interpretation; // a string table index +uint32 childLevel; // nesting level +}; +``` + +5. **PropertyHeader** (Gto::PropertyHeader). The PropertyHeaders, like the object and component headers, appear en masse in the file. The PropertyHeader contains the name, size, type, and dimension of the property. + +``` +enum DataType +{ +Int, // int32 +Float, // float32 +Double, // float64 +Half, // float16 +String, // string table indices +Boolean, // bit +Short, // uint16 +Byte // uint8 +}; +struct Dimensions +{ +uint32 x; +uint32 y; +uint32 z; +uint32 w; +} +struct PropertyHeader +{ +uint32 name; // string table index +uint64 size; +uint32 type; // DataType enum value +Dimensions dims; +uint32 interpretation; // string table index +}; +``` + +6. **Data** . The last section of the file contains all of the property data. The beginning and end of a properties data are not marked. The size must be consistent with the description of the property used in the PropertyHeader. + +In (Text) diagram form the file looks something like this: + + +| | +| --- | +| File Header | +| String Table | +| Object Header . . . | +| Component Header . . . | +| Property Header . . . | +| Property Data . . . | + +## Text Format + + +GTO has a text representation in addition to the binary representation. The text representation is designed for human use; it is intended to be easy to modify or create from scratch in a text editor. It is not intended to compete with XML formats (which are typically only human readable in theory) nor is it intended to be used in place of the binary format which is much faster and more economical for storage of large data sets. + +### Example of a Cube Stored as a Text GTO + + +Here’s the example from the overview section: a cube stored using the “polygon” protocol: + +``` +GTOa (4) +# this is a comment +cube : polygon (2) +{ +points +{ +float[3] position = [ [ -2.5 2.5 2.5 ] +[ -2.5 -2.5 2.5 ] +[ 2.5 -2.5 2.5 ] +[ 2.5 2.5 2.5 ] +[ -2.5 2.5 -2.5 ] +[ -2.5 -2.5 -2.5 ] +[ 2.5 -2.5 -2.5 ] +[ 2.5 2.5 -2.5 ] ] +float mass = [ 1 1 1 1 1 1 1 1 ] +} +elements +{ +byte type = [ 2 2 2 2 2 2 ] +short size = [ 4 4 4 4 4 4 ] +} +indices +{ +int vertex = [ 0 1 2 3 +7 6 5 4 +3 2 6 7 +4 0 3 7 +4 5 1 0 +1 5 6 2 ] +} +} +``` + +The first line of the file is an identifier to tell the parser what variety of GTO file it is: in this case GTOa which indicates a plain ASCII text file. Currently the parser can only handle ASCII encoding; a forthcoming version will allow UTF-8. + +Objects are declared using the syntax: + +``` +OBJECTNAME [ : PROTOCOL [ (PROTOCOL_VERSION) ] ] +{ +... object contents ... +} +``` + +The brackets enclose optional syntax. So the `PROTOCOL_VERSION` (including the parents) is optional. The `PROTOCOL` is also optional; if omitted (along with the colon) the protocol defaults to object. In the example, “cube” is the name of the object and “polygon” is the name of the protocol–the protocol version is 2. + +Components must be declared inside the object brackets or other components. The brackets denote a *namespace* which is either an object namespace or a component namespace. Component namespaces must always be declared inside of an object or component namespace. Object namespaces can only appear at the top level of the file; in other words, objects cannot be inside another namespace. + +Components are declared like this: + +``` +COMPONENTNAME [as INTERPRETATION] +{ + ... component contents ... +} +``` + +The INTERPRETATION can be any string. Properties can be declared inside of the component namespace optionally followed by nested component declarations. The property declaration is the most flexible; since some aspects of the property (like its size) can be determined by the parser from the property data, you can omit them. + +The property syntax in its most general form is: + +``` +TYPE[XS,YS,ZS,WS][SIZE] PROPERTYNAME as INTERPRETATION = values ... +``` + +The brackets around `XS,YS,ZS,WS` and `SIZE` are literal in this case; they actually appear in the file. As you can see from the example, some of the property declaration syntax is optional. The `SIZE` can usually be determined from the values so it may be omitted. The dimensions are assumed to be `1` (or scalar) if it is omitted. The as `INTERPRETATION` section of the declaration may also be omitted. + +What cannot be omitted is the `TYPE, PROPERTYNAME,` and the assignment of values. + +### How Strings are Handled in the Text Format + + +With the exception of keywords and type names, any string in the text GTO file can be either be quoted or non-quoted. Non-quoted strings are restricted to strings which do not represent numbers. In addition, if a string contains punctuation or whitespace, it must be quoted. For example, if the name of the object in the cube example was “four dimensional time-cube” it would have to be declared like this: + +``` +"four dimensional time-cube" : polygon +{ +... +} +``` + +There is one additional exception: if a string is also a keyword or type name, it must be quoted. For example, here’s an exceptional property declaration: + +``` +int "int" as "as" = 1 +``` + +In this case the quoted string “int” is being used as the property name, but because it is also the name of a GTO type, it must be quoted. The string “as” is being used as an interpretation string and must be quoted because “as” is also a keyword in the the GTO file. + +When in doubt quote. + +### Value Brackets + + +Generally, a property value and elements of the value are enclosed in brackets: + +``` +TYPE[DIMENSIONS] PROPERTYNAME = [ [a b ...] [d e ...] ... ] +``` + +In this documentation, the *value* of a property is everything to the right of the “=” and an *element* is a fixed size collection of numbers or strings. The *size* of a property is the number of elements in its value. So in the example above, the `[a b ...]` portion of the syntax is an *element* . + +Bracketing the property value is optional in one circumstance: when the number of elements in the property value is one. For example, these declarations are equivalent: + +``` +int foo = 1 +int foo = [1] +``` + +If the width of the type is not one (elements are not scalar), then brackets must be put around each element of the property. If the size is one but the width is not one, then the enclosing brackets are still optional: + +``` +int[2] foo = [1 2] +int[2] foo = [ [1 2] ] +``` + +If however the size of the property is greater than one, the enclosing value brackets are required: + +``` +# property of size 3 +int[2] foo = [ [1 2] [3 4] [5 6] ] +``` + +To declare a property with no value use empty brackets: + +``` +int foo = [] +``` + +### The Size of a Property + + +The size of a property can be declared as part of its type declaration: + +``` +int[1][4] foo = [1 2 3 4] +``` + +In this case, “foo” contains four scalar elements. Because the size was specified, the following would be a syntax error: + +``` +int[1][4] foo = [1 2 3 4 5] +``` + +The parser would complain because five elements were supplied even though the property was declared as having only four. If no size is specified than the parser will determine the size from the number of elements in the value: + +``` +int[1] foo = [1 2 3 4 5] +``` + +So in this case “foo” has five elements. Note that in order to declare the size specifically, you must also declare the element width—even if the width is one. In the last example, because we did not specify the size, the declaration could also have been: + +``` +int foo = [1 2 3 4 5] +``` + +In this case it is understood that the type is actually `int[1][5].` + +Additional dimensions can be added to make e.g. a matrix: + +``` +float[4,4] M = [1 0 0 0 +0 1 0 0 +0 0 1 0 +0 0 0 1] +``` + +This can be extended up to four dimensions: + +``` +byte[4,1920,1080] eightBit1080pImage = [ ... ] +float[3,32,32,32] floatingPoint3DLUT = [ ... ] +``` + +### Run Length Encoding of Values + + +In some cases, a value will contain many copies of an element. There is a special syntax for these cases; you can use an ellipsis to indicate that all remaining elements are identical. The ellipsis can only appear directly before the final bracket character. + +There is one restriction when using this syntax: the type of the property value must be completely specified (including the size of the property) and the value must be enclosed in brackets. For example: + +``` +int[1][100] mass = [1 ...] +``` + +The ellipsis is literal (its actually in the file as three dot characters) The property “mass” will be one for all 100 elements. If the element has a width greater than one: + +``` +float[3][100] velocity = [ [0 0 0] ... ] +``` + +The ellipsis is used in place of an element. The following will *not* work: + +``` +float[3][100] velocity = [ [0 ...] ... ] +``` + +The intention here is to make all of the velocity elements `[0 0 0]` . However, this syntax is not correct and will produce a parsing error. + +### Syntax Reference + + +The grammar for the text GTO file. *INT* is an integer constant. *FLOAT* is a floating point constant, with a possible exponent part. *STRING* is either a quoted or non-quoted string. All other values are literal. Double quoted strings are keywords. + +``` +file:: + "GTOa" object_list + "GTOa" ( INT ) object_list + +object_list:: + object + object_list object + +object:: + STRING { component_list_opt } + STRING : STRING { component_list_opt } + STRING : STRING ( INT ) { component_list_opt } + +component_list_opt:: + nothing + component_list + +component_list:: + component + component_list component + +component_block:: + nothing + property_list + component_list + property_list component_list + +interp_string_opt:: + nothing + "as" STRING + +component:: + STRING interp_string_opt { component_block } + +property_list:: + property + property_list property + +property:: + type STRING interp_string_opt = atomic_value + type STRING interp_string_opt = [ complex_element_list ] + +dimensions:: + INT + INT "," INT + INT "," INT "," INT + INT "," INT "," INT "," INT + +type:: + basic_type + basic_type [ dimensions ] + basic_type [ dimensions ] [ INT ] + +basic_type:: + "float" "int" "string" "short" "byte" "half" + "bool" "double" + +complex_element_list:: + nothing + element_list + element_list "..." + +element_list:: + element + element_list element + +element:: + atomic_value + [ atomic_value_list ] + +atomic_value_list:: + string_value_list + numeric_value_list + +atomic_value:: + string_value + numeric_value + +string_value_list:: + string_value + string_value_list string_value + +string_value:: + STRING + +numeric_value_list:: + numeric_value + numeric_value_list numeric_value + +numeric_value:: + float_num + int_num + +float_num:: + FLOAT + - FLOAT + +int_num:: + INT + - INT +``` + +## Types of Property Data + + +The GTO format pre-defines a small number of data types that can be stored as properties. The currently defined types are: + + +| | | | +| --- | --- | --- | +| `double` | 64 bit IEEE floating point. | [Property Type] | +| `float` | 32 bit IEEE floating point. | [Property Type] | +| `half` | 16 bit IEEE floating point | [Property Type] | +| `int` | 32 bit signed integer. | [Property Type] | +| `int64` | 64 bit signed integer. | [Property Type] | +| `short` | 16 bit unsigned integer. | [Property Type] | +| `byte` | 8 bit unsigned integer (char). | [Property Type] | +| `bool` | Bit or bit vector. Not currently implemented. | [Property Type] | +| `string` | The string type is stored as a 32 bit integer index into the GTO file’s string table. So storing a lot of strings (especially if there is a lot of redundancy) is reasonably cheap. All strings in the GTO file are stored in this manner. | [Property Type] | + +Each of these data types can be made into a vector of that type. For example the float data type can be made into a point `float[3]` or a matrix `float[16]` . To store a scalar element the size of the vector is 1. (e.g. `float[1]` ). + +In this document, the types are all specified as 2 dimensional arrays ala the C programming language. Here is a complete list of example type forms: + +* `float[3]` — the float triple type. +* `float[1] [1]` - a single floating point number. +* `float[3] []` - any number of float triples. +* `float[3] [3]` - three float triples. +* `float[16] []` - any number of a 16 float element. +* `float[4,4] [1]` - a 4x4 float matrix. +* `float[3,3] []` - any number of 3x3 float matrices. +* `float[4,512,128] [7]` - seven four component 512x128 images. +* `float[3,32,32,32] [1]` - a 3 component 32x32x32 volume. + +## Interpretation Strings + + +Each property can have an additional string stored with it called the “interpretation”. The intent is to allow applications to provide specific information about the property. For example, a property of type `float[4]` can be interpreted as a homogeneous 3D coordinate, a quaternion, or an RGBA value. The interpretation field can be used to distinguish between them. + +Why not just make new primitive GTO types for these? The format’s only purpose is storage of data. By decoupling the interpretation of the data from its storage, each application is allowed to make its own policy while maintaining flexibility for simpler applications. + +Here’s a simple example of `gtoinfo` output of a file with an image object in it created with `gtoimage` : + +``` + object "image" protocol "image" v1 + component "image" + property string[1][1] "originalFile" interpret as "filename" + property string[1][1] "originalEncoding" interpret as "filetype" + property string[1][1] "type" + property int[1][2] "size" + property float[3][199168] "pixels" interpret as "RGB" +``` + +Some of the stings will be application specific. Programs that generically edit GTO files should attempt to preserve the interpretation strings. + +It is not an error to define the interpretation for a property as the empty string—in other words, unspecified. + +The following strings are not currently part of the format specification but are used by the sample implementation. In a future release we may make these “official”. It’s ok to have multiple space separated strings in the interpretation strings (e.g. “4x4 row-major”). + + +| | | | +| --- | --- | --- | +| `coordinate` | The data can be of any width or type. For width N the data represents a point in N dimensional space. | [Interpretation String] | +| `normal` | The data can be of any width or type. For width N the data represents a unit vector prependicular to an N dimensional surface or in the case of N == 2, a curve. | [Interpretation String] | +| `4x4` | The width of the property data should be 16. The data is intended to be interpreted as a 4x4 matrix. For example, the **object.globalMatrix** property of the **Coordinate System** protocol would be a “4x4” property. | [Interpretation String] | +| `3x3` | The width of the property data should be 9. The data is intended to be interpreted as a 3x3 matrix. | [Interpretation String] | +| `row-major` | Indicates that matrix data is in row major ordering. | [Interpretation String] | +| `column-major` | Indicates that matrix data is in column major ordering. | [Interpretation String] | +| `quaternion` | The width of the property should be four. The data should be interpreted as a quaternion. Presumably the type of a quaternion property would be `float[4]` or `double[4]` since these are the only types that make sense. The first element of the data is the real part followed by the “i”, “j”, and “k” imaginary components. | [Interpretation String] | +| `complex` | The width of the property should be two. The data is interpreted as a complex number with the first element being the real part and the second element the imaginary part. | [Interpretation String] | +| `indices` | The data type should be an integral type. The property contains indices. | [Interpretation String] | +| `bbox` | The data type should have an even width. The property contains bounding boxes. | [Interpretation String] | +| `homogeneous` | The width of the property should be two or more. If the width is three, then the data is a two dimensional homogeneous coordinate. If the width is four, then the data is a three dimensional homogeneous coordinate. So for data of width `N` the data represents a homogeneous coordinate in `N-1` dimensions. | [Interpretation String] | +| `RGB` | The width of the property should be three. The data represents a color with red, green, and blue components. | [Interpretation String] | +| `BGR` | The width of the property should be three. The data represents a color with blue, green, and red components. (Reversed `RGB` ) | [Interpretation String] | +| `RGBA` | The width of the property should be four. The data represents a color (or pixel) with red, green, blue, and alpha components. | [Interpretation String] | +| `ABGR` | The width of the property should be four. The data represents a color (or pixel) with alpha, blue, green, and red components. (Reversed `RGBA` ) | [Interpretation String] | +| `bezier` | The property represents a 2D bezier curve for animation. The type should be a floating point type and the width six. Each element is a key frame value. | [Interpretation String] | +| `weighted` | In the case of bezier animation curves, the curve should be evaluated with weighted tangents. | [Interpretation String] | + +## Object Protocols + + +The Object data interpretation is not defined by the GTO format. However, there are currently some protocols in use that are well defined and these are documented here. Caveat emptor: gto files in the wild may contain more data than these protocols define, but they presumably will obey the protocol if they indicate it by name. It’s also possible that some objects may obey more than one protocol yet only indicate that they follow one. Unfortunately, some protocols also specify optional components and properties in case all of this was not confusing enough. + +Protocols also have a version number. The version number is an integer; there are no sub-versions. If there are significant changes to a protocol, the version number should be bumped. The version number is not meant as a method of making alternate protocols with the same name. We have had to make three modifications to the protocols since the file format was invented; one to the polygon protocol and one to the transform protocol, and the introduction of a new protocol (connections). The changes are documented in those sections. + +In this document, properties are all named “comp.prop”, where “comp” is the name of the component the property belongs to and “prop” is the name of the property. This is done to prevent ambiguity when two different properties in different components but with the same name exist. In the GTO file and when using the reader library only the property name will appear. + +There are two kinds of protocols: major and minor. Every object must have a major protocol that’s stored in the ObjectHeader—this is the main indicator of how to interpret the object data. In addition, the object may also have several minor protocols. These indicate optional data and how to interpret it. The next section describes how these are stored in the file. + +### Object Protocol + + +The name of the protocol as it appears in the ObjectHeader is “object” version 1. The protocol does not require any other protocols. Here it is: + + +| | | | +| --- | --- | --- | +| `object` | A container for properties which don’t fit into other component catagories well. A catch-all data “per-object” component. | [Required Component] | +| `float[16][1] object.globalMatrix` | The global world-space transform for the object. | [Optional Property] | +| `float[6][1] object.boundingBox` | The global world-space bounding box for the object. | [Optional Property] | +| `string[1][1] object.parent` | Name of this object’s parent in a scene heirarchy. | [Optional Property] | +| `string[1][1] object.name` | The name of the object. This name should be identical to the name in the ObjectHeader. | [Optional Property] | +| `string[1][] object.protocol` | Additional protocols. This property may contain the main protocol name and any other minor protocols that the object adheres to. If a protocol name appears in this property, the object must adhere to that protocol. Its not an error for a program to output this property with only the major protocol as its value; this is of course redundant since the protocol name is required by the ObjectHeader. It is also not an error for this property to exist but contain nothing. | [Optional Property] | +| `int[1][] object.protocolVersion` | Additional protocol version numbers. This property may exist if the **object.protocol** property exists. Each entry in this property corresponds to the same entry indexed in the **object.protocol** property. This property must contain the same number of elements that the **object.protocol** property does. | [Optional Property] | + +You may be asking why the **object** protocol exists at all. The name of an object is stored in the ObjectHeader in the file and in the C++ library is passed to the reader code. The “name” property is redundant right? Well yes. But some programs will output the name both in the ObjectHeader and in an **object** component as the property “name”. + +The main point of this protocol is to define the **object** component. This component is meant to hold data that is “per object” and which doesn’t really fit neatly into other components. The name is one such case. The coordinate system protocol also defines properties in the **object** component and the minor protocols are optionally stored here. + +### Coordinate System Protocol + + +The name of the protocol as it appears in the ObjectHeader is “transform” version 3 [1](#footnote-1) . The protocol requires the object protocol. Objects which obey the transform protocol will have global matrices and possibly a parent. + + +| | | | +| --- | --- | --- | +| `object` | From the “object” protocol. | [Required Component] | +| `float[16][1] object.globalMatrix` | A 4x4 matrix of floating point numbers. This matrix describes the world matrix of the coordinate system. | [Required Property] | +| `string[1][1] object.parent` | The name of an object to which this coordinate system is parented. Presumably this object (if it appears in the gto file) will also obey the **transform** protocol. If this property does not exist or the name is “” (the empty string) then the coordinate system presumably is a root coordinate system. [2](#footnote-2) | [Optional Property] | + +### Particle Protocol + + +The name of the protocol as it appears in the ObjectHeader is “particle” version 1. The protocol may include the object and transform protocols. + + +| | | | +| --- | --- | --- | +| `points` | The points component is transposable. That means that all of its properties are required to have the same number of elements. | [Required Component] | +| `float[3][] points.position` | | [Optional Property] | +| `float[4][] points.position` | The position property is intended to hold the position of the particle in its owncoordinate system or world space if it has no coordinate system. The element is either a 3D or 4D (homogeneous) point | [Optional Property] | +| `float[3][] points.velocity` | The velocity property – if it exists – should hold the velocity vector per-point in the same coordinate system that the “position” property is in. | [Optional Property] | +| `int[1][] points.id` | The “id” property should it exist will always be defined as an integer per particle (or other integral type if it ever changes). This number should be unique for each particle. Ideally, multiple GTO files with a point that has the same “id” property for a given particle animation should be the same particle. | [Optional Property] | + +The **particle** protocol defines the **points** component that many other protocols are derived from. For example, the **NURBS** protocol uses the points defined by the particle protocol as control vertices. There can be any number of properties associated with particles including string per-particle. + +The **points** component is marked transposable in the its ComponentHeader. This means that the properties in the component are guaranteed to have the same number of elements. Because of this, the data for the properties in a transposable component may be stored differently than other components. For example, the normal state of affairs is to write data like this: + +``` +position0 position1 position2 .... positionN +velocity0 velocity1 velocity2 .... velocityN +mass0 mass1 mass2 .... massN +``` + +So that you must read through all of the particle positions before you can read the first particle’s velocity. But this is not usually the best way to read particle data for rendering. You may want to cull the particles as you read them without storing the data. In order to do this the data needs to be laid out like this: + +``` +position0 velocity0 mass0 +position1 velocity1 mass1 +position2 velocity2 mass2 +... +``` + +In this case, each particle is scanned in one chunk allowing for optimizations. Obviously this complicates reading, but in the case of giga-particle renderers, this can be a huge memory savings. + +### Strand Protocol + + +A **strand** object contains a collection of curves. This is somewhat analogous to an object of protocol **particle** as described above. + + +| | | | +| --- | --- | --- | +| `points` | | [Required Component] | +| `float[3][] points.position` | The CVs which make up each curve. The number of CVs per curve can vary by curve type and size. | [Required Component] | +| `strand` | Information that is relevant to the *all* strands in the object. | [Required Component] | +| `string[1][] strand.type` | String describing curve type. Currently, supported values are linear for degree 1 curves, or cubic for degree 3 curves. | [Required Property] | +| `float[1][1] strand.width` | If each end of all curves is the same width, you can just specify that one number instead of the list as with **elements.width** below. | [Optional Property] | +| `elements` | Information that applies to each separate strand in the object. | [Required Component] | +| `int[1][] elements.size` | This is a list of the sizes of each curve in this object. For example, if there are two curves in this object, with 4 CVs and 3 CVs respectively, then: `elements.size = [ 4 3 ]` | [Required Property] | +| `float[2][] elements.width` | This is a list of the widths of each end of each curve. The width for each curve will be linearly interpolated over the length. | [Optional Property] | + +### NURBS Protocol + + +The name of the protocol as it appears in the ObjectHeader is “NURBS” version 1. The protocol requires the **particle** protocol and optionally includes the **object** and **transform** protocols. + + +| | | | +| --- | --- | --- | +| `points` | see **particle** protocol. The points describe data per NURBS control vertex. | [Required Component] | +| `float[3][] points.position` | | [Required Property] | +| `float[4][] points.position` | The position property holds the control point positions in its own coordinate system or world space if it has no coordinate system. The element is either a 3D or 4D (homogeneous) point. If the type is float[4] the fourth component of the element will be the rational component of the control point position. The control points are laid out in *v-* **major** order ( *u* iterates more quickly than *v* ). | [Required Property] | +| `float[1][] points.weight` | If the position property is of type `float[3][]` there may optionally be a “weight” property. This property holds the homogeneous (rational) component of the position. Older GTO writers may export data in this manner. The preferred method is to use a `float[4]` element position. | [Optional Property] | +| `surface` | Properties related to the definition of a NURBS surface are stored in this component. | [Required Component] | +| `float[1][2] surface.degree` | The degree of the surface in *u* and *v* . | [Required Property] | +| `float[1][] surface.uKnots` | | [Required Property] | +| `float[1][] surface.vKnots` | The NURBS surface knot vectors in *u* and *v* are stored in these properties. The knots are not piled. The usual NURBS restrictions on how numbers may be stored in the knot vectors apply. | [Required Property] | +| `float[1][2] surface.uRange` | | [Required Property] | +| `float[1][2] surface.vRange` | The range of the knot parameters in *u* and *v* . | [Required Property] | + +The **NURBS** protocol currently does not handle trim curves, points on surface, etc. Ultimately, the intent is to handle the trim curves and other nasties as NURBS curveson-surface which will be stored in additional components. UBS surfaces can be stored as NURBS with non-rational uniform knots. + +### Polygon Protocol + + +The name of the protocol as it appears in the ObjectHeader is “polygon” version 2 [3](#footnote-3) . The protocol requires the **particle** protocol and optionally includes the **object** and **transform** protocols. + +There are a number of alternative configurations of this protocol depending on the value of the **smoothing.method** property. All of these involve the placement of normals in the file. + + +| | | | +| --- | --- | --- | +| `points` | See **particle** protocol. The points describe data per vertex. | [Required Component] | +| `float[3][] points.position` | The positions for regular polygonal meshes are stored as `float[3]` . | [Required Property] | +| `float[3][] points.normal` | Normals per vertex. The **smoothing.method** property will have the value of *Smooth* if this property exists. Note that use of the *Smooth* smoothing method does not require that this property exists. If it does not the method is merely and indication of how the normals should be constructed. | [Optional Property] | +| `normals` | This property is required only if the **normals** component exists and the value of **smoothing.method** is *Partitioned* or *Discontinuous* . | [Required Property] | +| `elements` | The elements component is transposable. All properties in the elements component must have the same number of elements. Each element corresponds to a polygonal primitive. | [Required Component] | +| `byte[1][] elements.type` | Elements are modeled after the OpenGL primitives of the same name. The vertex order is identical to that defined by GL. The type numbers outside those given here are not defined but reserved for future use. So far, these are the define type numbers:
**0 –** Polygon General N-sided polygon. This can be used for any polygon that has 3 or more vertices.,
**1 –** Triangle A three vertex polygon.,
**2 –** Quad A four vertex polygon.,
**3 –** TStrip Triangle strip.,
**4 –** QStrip Quad strip., **5 –** Fan Triangle fan. | [Required Property] | +| `short[1][] elements.size` | The size of each primitive. Because the type is short, there is a limit of 65k vertices per primitive. | [Required Property] | +| `short[1][] elements.smoothingGroup` | This property may exist if the value of **smoothing.method** is *Partitioned* . In that case, this property indicates the smoothing group number associated with each element. These can be used to recompute the normals. These numbers are the same as those found in the Wavefront .obj file format’s “s” statements. A value of 0 indicates that an element is not in a smoothing group. | [Optional Property] | +| `float[3][] elements.normal` | Normals per element. The **smoothing.method** property will have the value of *Faceted* if this property exists. Note: the use of *Faceted* smoothing method does not require that this property exists. If it does not, the smoothing method is merely and indication of how the normals should be created. | [Optional Property] | +| `indices` | The indices component is transposable. All of its properties are required to have the same number of elements. Each entry in the indices component corresponds to a polygonal vertex. [4](#footnote-4) | [Required Component] | +| `int[1][] indices.vertex` | A list of all the polygonal vertex indices in the same order as the **elements.primtives** . The indices refer to the **points.position** property. So if the first polygonal element is a triangle and second is a general four vertex polygon then vertex indices will be something like:
`0 1 2 1 0 3 4 ...`
which would be grouped as:
`(0 1 2) (1 0 3 4) ...`
The first group is the triangle and the second the polygon. | [Required Property] | +| `int[1][] indices.st` | Similar to the vertex indices but indicates indices into *st* coordinates. These are usually stored in the “mappings” component but may also appear in the **points** component. | [Optional Property] | +| `int[1][] indices.normal` | Indices into stored normals if there are any. The **smoothing.method** property will have the value of *Partitioned* or *Discontinuous* if this property exists. | [Optional Property] | +| `mappings` | Contains parametric coordinates. The property names in mappings usually correspond to names found in the **indices** component but not always. For example **mappings.st** would be a `float[2][]` property holding texture coordinates indexed by **indices.st.** | [Optional Component] | +| `smoothing` | The smoothing component exists to hold the smoothing method and any ancillary data for the method. If there is no smoothing component (and hence no **smoothing** ) you can assume anything you want. | [Optional Component] | +| `int[1][1] smoothing` | There five defined smoothing methods (0 through 4). They are:
**0 – None** No smoothing method specified. No additional properties associated with normals will appear in the object.
**1 – Smooth** One normal at every vertex. There will be a `float[3][]` **normal** property as part of the **points** component. Each vertex has a unique normal.,
**2 – Faceted** One normal for each face. There will be a `float[3][]` **normal** property in the **elements** component. Each element has a unique normal.,
**3 – Partitioned** Same as the Wavefront .obj smoothing groups. There will be a **normals** component containing a `float[3][]` **normal** property and an `int[1][]` **normal** property in the **indices** component. Each element vertex will have an index into the **normals.normal** property.,
**4 – Discontinuous** Like *Partitioned* but with additional lines and points of discontinuity. The same properties that hold the *Partitioned* information will hold the *Discontinuous* information. There will also be a component called **discontinuities** which will have a `int[1][]` property called **indices** indicating the points and lines of discontinuity. | [Required Property] | + +### Subdivision Surface Protocols + + +The name of the protocol as it appears in the ObjectHeader is “catmull-clark” or “loop” depending on the intended subdivision scheme. The protocol requires the **polygon** protocol. + +The smoothing and any normals properties on the **polygon** protocol should be ignored if they exist. + +The protocol indicates how the surface should be treated. Note that the canonical element type for each of the two schemes is not guaranteed to be the only element type stored in the file. For catmull-clark this means that triangles and general polygons will need to be made into quads. Similarily loop surfaces may have quads and other non-triangle primitives that need to be triangulated. + +These protocols do not currently define methods for storing edge creasing parameters. Disclaimer: there are restrictions on what kind of topology surfaces are allowed to have for a given renderer (for example). In most cases surfaces need to be manifold. Some applications can deal with special cases better than others. + +### Image Protocol + + +The Image protocol describes image data in the form of an object. This data makes it possible to store texture maps, backgrounds, etc, directly in the GTO file. + +When images are stored in a GTO file, use of Gzip compression is highly recommended if the data is unencoded. The supplied Reader and Writer classes default to using zlib compression. + +If the image data is encoded, its better not to use compression on the GTO file (especially if the file contains only image data). + + +| | | | +| --- | --- | --- | +| `image` | The image data and other information will be stored in the **image** component. | [Required Component] | +| `int[1][] image.size` | The size (and dimension) of the image. There will be N sizes in this property corresponding to the N dimensions of the image. | [Required Property] | +| `string[1][1] image.type` | The image type. For interactive purposes, the image channels may correspond to a particular fast hardware layout.
`RGB` Three channels corresponding to red, green, and blue in that order.,
`BGR` Three channels corresponding to blue, green, and red in that order.
`RGBA` Four channels corresponding to red, green, blue, and alpha in that order.
`ABGR` Four channels corresponding to alpha, blue, green, and red in that order.
`L` One channel corresponding to luminance.
`HSV` Three channels corresponding to hue, saturation, and value. (The HSV color space).
`HSL` Three channels corresponding to hue, saturation, and lightness. (The HSL color space).
`YUV` Three channels corresponding to the YUV color space. | [Required Property] | +| `int[1] image.cs` | The coordinate system of the image. The value of **image.cs** can be any one of the following:
**0 – Lower left origin.** : The first pixel in the image data is the lower left corner of the image data and corresponds to NDC coordinate (0,0).
**1 – Upper left origin.** : The first pixel in the image data is the upper left corner of the image data and corresponds to NDC coordinate (0,0). | [Optional Property] | + +Any one of the following properties are required to hold the actual image data: + + +| | | | +| --- | --- | --- | +| `byte[N][] image.pixels` | | [Property] | +| `short[N][] image.pixels` | | [Property] | +| `string[1][1] image.type` | | [Property] | +| `half[N][] image.pixels` | The element width determines the number of channels in the image. For example the type `byte[3][]` indicate a 3 channel 8-bit per channel image. The number of elements in this property should be equal to `image.size[0] * image.size[1] * ... image.size[N]` where **image.size** is the property defined above. | [Property] | + +#### Additional Image Properties Used by GTV Files. + +The base GTO library does not deal with encoded image data or tiling of images. GTV is a specialization of the GTO format for storing movie frames. Some of the GTV properties are documented here. (See documentation for the GTV library for more info). + + +| | | | +| --- | --- | --- | +| `string[1] image.encoding` | If the pixel data is encoded this property will indicate a method to decode it. Typical values are “jpeg”, “jp2000”, “piz”, “rle”, or “zip”. The pixels will be stored in the **image.pixels** as `byte[1][]` . | [OptionalProperty] | + +### Material Protocol + + +The name of the protocol as it appears in the ObjectHeader is “material”. The material protocol groups a parameters and a method (shader) for rendering. The material protocol can optionally include the **object** protocol. + +The material definition is renderer and pipeline dependant. Material assignment is implemented using the **connection** protocol. See Section Inter-Object. + +The **material** protocol is intended for use with software renderers. Interactive material definitions may be more easily defined on the assigned object. + + +| | | | +| --- | --- | --- | +| `material` | Properties unrelated to parameters appear in the **material** component. | [Required Component] | +| `string[1][1] type` | The value of the **material.type** property is renderer dependant. For a RIB renderer, the value of type might be “Surface”, “Displacement”, “Atmosphere” or a similar shader type name. | [Required Property] | +| `string[1][1] shader` | The name of the shader. For RenderMan-like renderers this might be the name of an “.sl” file. | [Optional Property] | +| `string[1][1] genre` | A property to further identify the material. This is most useful for identifying the target renderer for a material. | [Optional Property] | +| `parameters` | The set of parameters corresponding to the **material.type.** | Optional Component] | + +### Group Protocol + + +### Inter-Object Connection Protocol + + +The name of the protocol as it appears in the ObjectHeader is “connection” version 1. + +Files which employ the **connection** protocol will typically contain a connection object with the special cookie name “:connections” indicating the purpose of the object as well as preventing namespace pollution. See Section Special Cookies. + +Each component in a connection object is a connection type. For example, the “par- ent of” connection type is used to represent transformation hierarchies. In a connection object, there will be a single component called “parent of” which will contain the required properties **parent_of.lhs** and **parent_of.rhs** at a minimum. Some connection types may have additional data in the form of additional properties. + +Connection components are transposable. The number of elements in properties comprising a connection component will be consistent. So a single “parent of” component can encode an entire scene transformation hierarchy. + +Connection components have the following properties. Note that where **connection_type** occurs in the property name, you would substitute in the actual name of the connection type. (“parent of” for example). + +| | | +| --- | --- | +| `string[1][] connection_type.lhs` | [Required Property] | +| `string[1][] connection_type.rhs` | [Required Property] | + +The left-hand-side and right-hand-side of the connection. + +* If the connection is directional, then an arrow indicating the direction would have its tail on the left-hand-side and its head pointing at the right-hand-side. +* If the connection type does not require a direction then these properties are still used to describe the two ends of the connection. +* Each entry will be the name of an object. There is no requirement that the ends of the connection exist in the file. For example, one end of the connection could be an image on disk. +* The empty string is a valid value. You could think of the empty string as indicating a grounded connection. +* It is ok for both ends of the connection to have the same value. + +The GTO specification includes a couple of basic connection types. + +#### Transformation hierarchies. + +The “parent of” connection type is used to store transformation hierarchies. The connection type requires only the **lhs** and **rhs** properties. Transformation hierarchies are usually tree structures, but can also be DAGs (as is the case with Maya or Inventor). + +Using “parent of” as a cyclic generalized network connection is probably an error for most applications. To be safe the topology of a “parent of” network should be a tree. + +#### Material Assignment + +The “material” connection type indicates a material assignment to an object. The left-hand-side name is a renderable object in a GTO file The right-hand-side is the name of a material object in a GTO file. + +#### Container Assignment + +The “contains” connection type indicates membership in a group or similar type of container object. The LHS is the group or container, the RHS is the object which is a member. + +### Difference File Protocol + + +If the **object.protocol** property contains the string “difference” then the object contains difference data; the data is relative to some other reference file. + +For example, for animated deforming geometry its advantageous to write a reference file for geometry in its natural undeformed state then write only the **points.position** property in a gto file per frame to store animation. The **difference** minor protocol can apply to any major protocol. + +If a reference file and a difference for file it exists, you can reconstruct the file represented by the difference file using the `gtomerge` command. See Section gtomerge. + +### Sorted Shell File Protocol + + +If the **object.protocol** property contains the string “sorted” and the object’s major protocol is **polygon** then the object contains sorted shell data. + +This protocol guarantees that the vertices and elements of shells—isolated sections of polygonal geometry—will be continguous in the **points** and **elements** components of the object. + + +| | | | +| --- | --- | --- | +| `shells` | The **shells** component is transposable. Each property in the component should have the same number of elements. | [Required Component] | +| `int[1][] shells.vertices` | The number of contiguous vertices that make up the Nth element’s shell. | [Required Property] | +| `int[1][] shells.elements` | The number of contiguous elements that make up the Nth element’s shell. | [Required Property] | + +### Channels Protocol + + +This minor protocol declares data mapped onto geometric surfaces. Usually the data is mapped using one of the parameterizations found in the **mappings** component of polygonal or sub-d geometry or possibly using the natural parameterization of a surface as is often the case with NURBS. + +Each declared channel appears as a `string[1] []` property of a **channels** component on the geometry. The name of the property is the name of the channel. The property should contain at least one element. + +The first element of the property should indicate the name of the mapping to use. This is either the name of one of the properties in the **mappings** component or “natural” indicating that the natural parameterization of the surface should be used. + +The second and subsequent elements should contain the name of data to map. This could be a texture map file on disk, an image object in the GTO file, or a special cookie string. The lack of second element can be used as a special cookie. + + +| | | | +| --- | --- | --- | +| `channels` | The component holds the names of all the channels on the geometry. | [Required Component] | + +#### Example + +Here is a cube with “color”, “specular”, and, “bump” channels assigned. + +``` +Object "cube" protocol "polygon" + Component "points" + Property float[3][8] "position" + Component "elements" + Property byte[1][8] "type" + Property short[1][8] "size" + Component "indices" + Property int[1][32] "vertex" + Property int[1][32] "st" + Component "mappings" + Property float[2][24] "st" + Component "channels" + Property string[1][2] "color" + Property string[1][2] "specular" + Property string[1][2] "bump" +``` + +The contents of the “channels” properties might be: + +``` +string[1] cube.channels.color = [ "st" "cube_color.tif" ] +string[1] cube.channels.specular = [ "st" "cube_specular.tif" ] +string[1] cube.channels.bump = [ "st" "cube_bump.tif" ] +``` + +### Animation Curve Protocol + + +The animation curve protocol defines a single component called **animation** in which each property holds an animation curve or data stream. The property’s interpretation string indicates how the data should be evaluated. + +#### Example + +Here is a cube with animation curves. + +``` +Object "cube" protocol "polygon" + Component "points" + Property float[3][8] "position" + Component "elements" + Property byte[1][8] "type" + Property short[1][8] "size" + Component "indices" + Property int[1][32] "vertex" + Component "animation" + Property float[6][2] "xtran" interpret as "bezier" + Property float[6][2] "ytran" interpret as "bezier" + Property float[6][2] "ztran" interpret as "bezier" + Property float[6][5] "xrot" interpret as "bezier" + Property float[6][8] "yrot" interpret as "bezier" + Property float[6][10] "zrot" interpret as "bezier" + Property float[1][100] "xscale" interpret as "stream" +``` + +In version 1, the transform protocol’s object.globalMatrix property used to be of type `float[1] [16]` . This was a mistake that has been corrected in version 2. 2 + +In version 3 of the **transform** protocol, the **object.parent** property is redundant and therefor deprecated. The **connection** protocol handles the transformation hierarchy information and in a much more elegant manner See Section Inter-Object. + +In version 1 of the polygon protocol, the **element.size** and **element.type** properties were combined into an **element.primitive** property. We felt that this was adding unnecessary complexity and because the primitive property was an int, it was taking up extra space. + +The indices component in a polygonal object contains values which are analogous to the RenderMan face varying type modifier. + +## Naming Conventions + + +GTO files can contain cross references to parts of themselves, objects outside the file, or virtual/logical objects in applications. Because of the potential morass that can result from complete free-form naming, there are conventions which are part of the file specification. + +Failure to follow the guidelines does not mean a GTO file is ill-formed; there’s always a good reason to ignore guidelines. But having a basis for consistency is usually a good idea. + +Some of these topics are a bit “advanced” in that they build off ideas that present themselves after using the file format for a while. If you are just learning about the format, consider this a reference section and skip it. If you’re trying to decrypt a complicated GTO file with strange garbled naming, then this section is for you. + +### Valid Names + + +Names should be valid C identifiers, but should not contain the dollar-sign character ($). This means that no whitespace or punctuation is allowed. + +Note that this does *not* apply to protocol names. + +There is nothing in the sample `Reader` or `Writer` classes which enforces the valid name guideline. However, some applications (Maya) cannot handle names with whitespace and/or punctuation. So plug-ins which implement GTO reading/writing will have to enforce the application’s specific naming requirements. + +This guideline is broken by Section Special Cookies. It is also broken by Section Cross References. + +### Exactly Specifying a Property or Component + + +By convention, the full name or path name of a property is referred to like this: + +``` +OBJECTNAME.[COMPONENTNAME.]+PROPERTYNAME +``` + +where there can be any number of COMPONENTNAME parts. When indicating a property name relative to an object then: + +``` +[COMPONENTNAME.]PROPERTYNAME +``` + +should suffice. In this manual, names of components and properties are disambiguated using the dot notation. In addition, this is the format of output from the gtoinfo command. There is nothing about the GTO file itself which relates to this notation other than the cross-referencing naming convention discussed below. + +### Indicating Special Handling + + +Some objects, components, or properties in the GTO file may contain data for which names are not particularly useful or that may simply pollute the object or component namespace. + +In other cases (component names most notably) the name may be used as information necessary to interpret data associated with it. + +In order to distinguish these names from run-of-the-mill names, you should include a colon in the name. Names with colons are considered “special cookie” names and objects which have them may be handled differently than other objects. + +The **connection** object protocol, for example, requires that a special file object exist to hold data. This object is not necessarily related to a logical object in the application, it is just a container for the connection data. These objects are named using the special cookie syntax. Usually the name is “:connections”. See Section Inter-Object. + +There is no rule regarding the placement of the colon in the name; it can appear anywhere in the name that is useful for the application. However, if the entire name is a special cookie—there is not additional information encoded in the name beyond itself—the recommend form is to have the colon be the first character. + +### Cross References Encoded in Names + + +Sometimes there is a need to have a property or component *refer* to another property, component, or object in the file (or somewhere else). + +To cross reference the data in one property with another, simply name the property the full (or partial) path to the referenced property. For example, here’s the output of `gtoinfo` on a GTO file which has cross referencing properties: + +``` +object "gravity" protocol "gravity" v1 + component "field" + property float[3][1] "direction" + property float[1][1] "magnitude" + component ":datastream" + property float[3][300] "field.direction" + property float[1][300] "field.magnitude" +``` + +As you can guess, the intention here is that the properties called “field.direction” and “field.magnitude” in the “:datastream” component are data that is associated with the properties “direction” and “magnitude” in the “field” component. + +## Issues and Questionable Aspects of the Format + + +* There are currently no (publicly available) tools which verify that a file claiming to follow some protocol is correct. +* There is no 3D curve(s) protocol defined. +* The **NURBS** protocol does not handle trim curves. See Section NURBS Surfaces. +* The format does not contain dedicated space for auxillary information like the name and version of the program that wrote the file, the original owner, copyright information, etc. However, our tools use the string table for these type of data—since its not an error have an unused interned string, we store the data as such. In our opinion, this is a fairly innocuous method. You can read unreferenced strings by using the `gtoinfo` command with the `-s` option. Note that these strings are often lost when programs read and write the file. See Section gtoinfo. +* Although the format specification includes transposable components (those marked with the Gto::Matrix flag may be transposed), the current reader/writer library does not handle files with transposed components. It does handle components that are marked as Gto::Matrix but not transposed. See Section Particles. +* The use of special cookie names and special cross-reference names seems to seriously complicate the format if the protocol is not carefully conceived. For example, using `gtomerge` to merge files containing connections does not work—the connections are merged like all the other data in the file. The correct behavior would be to combine the connections, but merge the other object data. Perhaps this is just a case for integrating `gtocombine` into `gtomerge` ? +* Future versions should incorporate some form of check sum or some similar mechanism to do better sanity checking. +* There are many examples of properties whose data indexes into other property data. The most obvious of these are the polygon protocol **indices** properties. In order to combine gto files (concatenate polygonal data together for example) its necessary to know which properties are indexes and which are not. Index properties must be offset to be combined. +* The Boolean (bit fields) and Half data types are not implemented in the supplied writer library. Both of these types are useful in compressing geometric (and image) data. +* Material, Texture, and similar assignments and storage are usually very specialized at any particular production facility. The idea that a single method of encoding this information can be determined or enforced—or even usefully be stored in a GTO file—is not realistic. However, we hope that some method can be determined that at least preserves a good portion of common data for transfer. + + +## C++ Library + + +The GTO Reader/Writer library is written in a subset of C++. The intention was to make the library as portable as possible. Unfortunately we have only tried it on platforms that support gcc 2.95 and greater. It is known to work on various Linux versions and macOS. In either case it has been compiled with gcc. + +### Gto::Reader class + + +The Reader class (in namespace Gto) is designed as a fill-in-the-blank API. The user of the class derives from it; the base class defines a number of virtual functions which pass data to the derived class and ask the derived class questions about what data it wants. + +The Reader class handles most of the difficult work in reading the file like keeping track of headers, sizes of properties, and the order of data. In addition, it handles the string table and looking up property string values. If the file was written by a machine with different sex (endianess) it will translate the data for you. + +In addition, you can compile the GTO library with zlib support. This enables the Reader class to read gzipped GTO files natively and the Writer class to write them. This can be a significant space savings on disk and on saturated networks can make file loading faster. You can also pass a C++ istream object to the Reader if you want to read “in-core”. + +As the file is read, the Reader class will call its virtual functions to declare objects in the file to the derived class. The derived class is expected to return a non-null pointer if it wishes to later receive data for that object. + + +| | | | +| --- | --- | --- | +| `Reader::Reader` *(unsigned int `mode` )* | The constructor argument mode indicates how the reader will be used. This value is a bit vector of the following or’ed flags: +**`Reader::None`** +The reader will be used in its standard *streaming* mode. The reader will attempt to read all the data in the file. This is the default value (or 0). +**`Reader::HeaderOnly`** +The reader will stop once it has read the header sections of the GTO file. This is an optimization that applies to binary files only. This option is ignored when reading a text file. +**`Reader::RandomAccess`** +The reader will read the header sections but not the data, however, it will initialize for use of the `Reader::accessObject()` function. Only binary GTO files can be read using the random access mode. +**`Reader::BinaryOnly`** +Only binary GTO files will be accepted by reader. +**`Reader::TextOnly`** +Only text GTO files will be accepte by reader. | [Constructor] | +| `Reader::~Reader ()` | Closes file if still open. | [Destructor] | +| `bool Reader::open` *(const char\* `filename` )* | Open the file. The Reader will attempt to open file filename. If the file does not exist and zlib support is compiled in, the Reader will attempt to look for filename.gz and open it instead. | [Method] | +| `bool Reader::open` *(std::istream&, const cha\* `name` )* | Reads the GTO file data from a stream. The *name* is supplied to make error messages make sense. | [Method] | +| `void Reader::close ()` | Close the file and clean up temporary data. If the stream constructor was used, the stream is *not* closed. | [Method] | +| `std::string& Reader::fail` *(std::string `why` )* | Sets the error condition on the Reader and sets the human readable reason to *why* . | [Method] | +| `std::string& Reader::why ()` | Returns a human readable description of why the last error occured. (Set by the `fail()` function). | [Method] | +| `const std::string& Reader::stringFromId` *(unsigned int)* | Given a string identifier, this method will return the actual string from the string table. | [Method] | +| `const StringTable& Reader::stringTable ()` | Returns a reference to the entire string table. | [Method] | +| `bool Reader::isSwapped ()` *const* | Returns true if the file being read needed to be swapped. This occurs if the machine the file was written on is a different sex than the machine reading the file (for example a Mac PPC written file read on an x86 GNU/Linux box). | [Method] | +| `unsigned int Reader::readMode ()` *const* | Returns the mode value passed into the Reader constructor. | [Method] | +| `const std::string& Reader::infileName ()` *const* | Returns the name of the file or stream being read. This is the value passed in to the `Reader::open()` function. | [Method] | +| `std::istream* Reader::in ()` *const* | Return the input stream created by or passed into `Reader::open()` . If the GTO file is compressed binary, this function will return NULL. | [Method] | +| `int Reader::linenum ()` *const* | For text GTO files, the return value will be the current line being parsed. For binary GTO files, the return value is always 0. | [Method] | +| `int Reader::charnum ()` *const* | For text GTO files, the return value will be the current char column (in the current line) being parsed. For binary GTO files, the return value is always 0. | [Method] | +| `Header& Reader::fileHeader ()` *const* | Returns a reference to a Gto::Header structure corresponding to the file currently being read. This function is required by the text file parser. The function may disappear from future versions. See the `Reader::header()` function below for a better way to get header information. | [Method] | + +The following functions are called by the base class. + + +| | | | +| --- | --- | --- | +| `void Reader::header` *(const `Header& header` )* | This function is called by the Reader base class right after the file header has been read (or created). | [Virtual] | +| `void Reader::descriptionComplete ()` | This function is called after all file, object, component, and property structures have been read. For binary files, this is just before the data is read. For text files, this is after the entire file has been read. | [Virtual] | + +The following functions return a `Reader::Request` object. This object takes two parameters: a boolean indicating whether the data in question should be read by the reader and a second optional data `void*` argument of user data to associate with the file data. + + +| | | | +| --- | --- | --- | +| `Reader::Request::Request` *(bool `want` , void\* `data` )* | *want* value of true indicates a request for the data in question. *data* can be any void\*. *data* is meaningless if the *want* is false. | [Constructor] | +| `Reader::Request Reader::object` *(const std::string& `name` , const std::string& `protocol` , unsigned int `protocolVersion` , const ObjectInfo& `header` )* | This function is called whenever the Reader base class encounters an ObjectHeader. The derived class should override this function and return a Request object to indicate whether data should be read for the object in question. If it requests not to have data read, the Reader will not call the corresponding component() and property() functions. | [Virtual] | +| `Reader::Request Reader::component` *(const std::string& `name` , const ComponentInfo& `header` )* | This function is called when the Reader base class encounters a ComponentHeader in the GTO file. If the derived class did not express interest in a particular object in the file by returning `Request(false)` from the object() function, the components of that object will not be presented to the derived class. The derived class should return `Request(true)` to indicate that it is interested in the properties of this *component* . | [Virtual] | +| `Reader::Request Reader::property` *(const std::string& `name` , const char* `interpString` , const PropertyInfo& `header` )\* | This function is called when the Reader base class encounters a PropertyHeader in the GTO file. If the derived class did not express interest in a particular object or the component that the property belongs to, the properties of that component will not be presented to the derived class. The derived class should return `Request(true)` to indicate it is interested in the property data. | [Virtual] | +| `void* Reader::data` (const PropertyInfo&, size_t `byts` ) | This function is called before property data is read from the GTO file. The function should return a pointer to memory of at least size bytes into which the data will be read. The type, size, width, etc, of the data can be obtained from the `PropertyInfo` structure. | [Virtual] | +| `void Reader::dataRead` *(const PropertyInfo&)* | This function is called after the `data()` function if the data was successfully read. | [Virtual] | + +If you are using the Reader class in `Reader::RandomAccess` mode, you may call these functions after the read function has returned: + + +| | | | +| --- | --- | --- | +| `Reader::Objects& Reader::objects ()` | Returns a reference to an std::vector of Reader::ObjectInfo structures. These are only valid after `Reader::open()` has returned. You can use these structures when calling `Reader::accessObject()` . | [Method] | +| `const Reader::Components& Reader::components ()` | Returns a reference to an std::vector of Reader::ComponentInfo structures. These are only valid after `Reader::open()` has returned. This method is most useful when deciding how to call the `accessObject` function. | [Method] | +| `const Reader::Properties& Reader::properties ()` | Returns a reference to an std::vector of Reader::PropertyInfo structures. These are only valid after `Reader::open()` has returned. This method is most useful when deciding how to call the `accessObject` function. | [Method] | +| `Reader::Request Reader::property` *(const std::string& `name` , const char* `interpString` , const PropertyInfo& `header` )\* | This function is called when the Reader base class encounters a PropertyHeader in the GTO file. If the derived class did not express interest in a particular object or the component that the property belongs to, the properties of that component will not be presented to the derived class. The derived class should return `Request(true)` to indicate it is interested in the property data. | [Method] | +| `void Reader::accessObject` *(const ObjectInfo&)* | Calling this function on a GTO file opened for `RandomAccess` reading will cause the reader to seek into the file just for the data related to the object passed in. This is most useful when the objects’ data cannot be held in memory and the order of retrieval is unknown. The reader attempts to be efficient as possible without using too much | [Virtual] | + +### Gto::Writer class + + +The Writer class (in namespace Gto) is designed as an API to a state machine. You indicate a conceptual hierarchy to the file and then all the data. The writer handles generating the string table, the header information, etc. + +The following is an example that outputs a polygon cube using the **polygon** protocol. + +``` +float points[3][] = +{ { -2.5, 2.5, 2.5 }, { -2.5, -2.5, 2.5 }, + { 2.5, -2.5, 2.5 }, { 2.5, 2.5, 2.5 }, + { -2.5, 2.5, -2.5 }, { -2.5, -2.5, -2.5 }, + { 2.5, -2.5, -2.5 }, { 2.5, 2.5, -2.5 } }; + +unsigned char type[] = { 2, 2, 2, 2, 2, 2 }; +unsigned char size[] = { 4, 4, 4, 4, 4, 4 }; + +int indices[] = {0, 1, 2, 3, 7, 6, 5, 4, + 3, 2, 6, 7, 4, 0, 3, 7, + 4, 5, 1, 0, 1, 5, 6, 2 }; + +Gto::Writer writer; +writer.open("cube.gto"); + +writer.beginObject("cube", "polygon", 2); // polygon version 2 + + writer.beginComponent("points"); + // will write 8 float[3] positions + writer.property("positions", Gto::Float, 8, 3); + writer.endComponent(); + + writer.beginComponent("elements"); + // one per face + writer.property("size", Gto::Short, 8, 1, 1); + writer.property("type", Gto::Byte, 8, 1, 1); + writer.endComponent(); + + writer.beginComponent("indices"); + // one per vertex per face + writer.property("vertex", Gto::Int, 24, 1, 1); + writer.endComponent(); + +writer.endObject(); + +// repeat writer object blocks if more objects + +// output all the data in order declared + + writer.beginData(); + writer.propertyData(type); + writer.propertyData(size); + writer.propertyData(indices); + writer.endData(); +``` + + +| | | | +| --- | --- | --- | +| `Writer::Writer ()` | Creates a new Writer class object. Typically you’ll make one of these on the stack. This constructor requires you call the open function to actually start writing the file. | [Constructor] | +| `Writer::Writer` *(std::ostream&)* | Creates a new Writer class object which will output to the passed C++ output stream. | [Constructor] | +| `Writer::~Writer ()` | Closes file opened with the `open()` function if still open. The destructor will not close any passed in output stream. | [Destructor] | +| `bool Writer::open` *(const char* `filename` , FileType `mode` \= CompressedGTO)\* | Open the file. The Writer will attempt to open file *filename* . If the file is not writable for whatever reason, the function will return false. If *mode* is `CompressedGTO` (the default value), the Writer class will output a binary compressed file. If the value is `BinaryGTO` the file will be binary uncompressed. If *mode* is `TextGTO` a text GTO file will be written. Compressed GTO files can be uncompressed manually using `gzip` . Compression is available only if the library is compiled with zlib support. | [Method] | +| `bool Writer::open` *(const char\* `filename` , bool compress = `true` )* | This function exists for backwards compatibility. Use the other `open()` function instead. This function can open a file for binary output only (it cannot write a text GTO file). | [Method] | +| `void Writer::close ()` | Close the file and clean up temporary data. If the stream constructor was used, the stream is *not* closed. | [Method] | +| `void Writer::beginObject` *(const char\* `name` , const char\* `protocol` , unsigned int `version` ) const* | Declares an object. Its components and properties must be declared before `endObject()` is called. The *name* is the name of the object as it will appear in the gto file. The *protocol* is the protocol string indicating how the object data will be interpreted and the *version* number indicates the protocol version. The Writer class does not verify that the data output conforms to the protocol. | [Method] | +| `void Writer::beginComponent` *(const char\* `name` , bool `transposed` \=false)* | Declares a component. The component properties must be declared before a call to endComponent(). The *name* is the name of the component as it will appear in the gto file. The *transposed* flag is optional and indicates whether or not the component property data should be output transposed or one property at a time (the default). | [Method] | +| `void Writer::property` *(const char\* `name` , Gto::DataType `type` , size_t `numElements` , size_t `partsPerElement` \=1, const char\* `interpString` \=0)* | Declare a property. The *name* is the name of the property as it appears in the gto file. The *type* is one of `Gto::Double` , `Gto::Float` , `Gto::Int` , `Gto::String` , `Gto::Byte` , `Gto::Half` , or `Gto::Short` . *numElements* indicates the number of elements of size *partsPerElement* that will be in the property data. So for example, if the property is declared as a Gto::Float of with *partsPerElement* of 3 and there 10 of them, then the writer will expect an array of 30 floats when the propertyData is finally passed to it. The last argument *interpString* is an optional interpretation string that can be stored with the property. | [Method] | +| `void Writer::endComponent ()` | Closes the declaration of a component started by `beginComponent()` . | [Method] | +| `void Writer::endObject ()` | Closes the declaration of an object started by `beginObject()` . | [Method] | +| `void Writer::intern` *(const char\* `string` )* | Declares a string to the Writer for inclusion in the file string table. When writing properties of type `Gto::String` , its necessary to call this function before the `beginData()` is called. Each string in the property data must be interned. When outputing the property, the property will be an array of `Gto::Int` in which each int is the result of the `lookup()` function which retrieves a unique int corresponding to interned strings. | [Method] | +| `void Writer::intern` *(const std::string& `string` )* | Same as above, but takes an `std::string` . | [Method] | +| `int Writer::lookup` *(const char\* `string` )* | Retrieve the identifier of the previously interned string *string* . | [Method] | +| `int Writer::lookup` *(const std::string& `string` )* | Same as above, but takes an `std::string&` . | [Method] | +| `void Writer::beginData ()` | Begins data declaration to the Writer class. Only calls to `lookup()` , `propertyData()` , `propertyDataInContainer()` , and `endData()` are legal after `beginData()` is called. | [Method] | +| `void Writer::propertyData` *(const TYPE\* `type` )* | `propertyData()` is a template function which takes a pointer to continuously stored data. The data must be the same as declared earlier by the `property()` function. Calls to `propertyData()` and `propertyDataInContainer()` must appear in the same order as the `property()` declarations calls. | [Method] | +| `void Writer::propertyDataInContainer` *(const TYPE& `container` )* | `propertyDataInContainer()` is a template function which takes an stl-like container as an argument. The data must be the same as declared earlier by the `property()` function. Calls to `propertyData()` and `propertyDataInContainer()` must appear in the same order as the `property()` declarations calls. This function is a convenience function; it calls `propertyData()` to actually output the data. This function may make a copy of the data in the container. | [Method] | +| `void Writer::endData ()` | Closes the definition of data started by `beginData()` and finishes writing the gto file. | [Method] | + +### Gto::RawDataReader/Gto::RawDataWriter classes + + +These classes provide a quick method of reading the contents of a GTO file into memory for basic editing. The RawDataReader and RawDataWriter both use the same very primitive data structure that can be found in the RawData.h file. For examples of use, see `gtomerge` and `gtofilter` source code. + +The RawData class shows how to both read and write using the supplied classes. In addition the reader subclass shows how to convert string data. + +## Python Module + + +The gto module implements a reader/writer library for the Python language. The module is implemented on top of the C++ reader and writer classes. The API is similar to the C++ API, but takes advantage of Python’s dynamic typing to “simplify” the design. The Python module also implements a significant number of safety checks not present in the C++ library, making it an ideal way of exploring the Gto file format. + +### gto.Reader + + +The Reader class is designed as a fill-in-the-blank API much like the C++ library. The user of the class derives from it; the base class defines a number of functions which you override to pass data to the derived class and receive data from it. + +As the file is read, the Reader class will call specific functions in itself to declare objects in the file. The derived class is handed data or asked to return whether or not it is interested in specific properties in the file. + +The biggest difference from the C++ Reader class is that the `data()` method of the C++ class, which returns allocated memory for the library to read data into, cannot be overloaded in Python. Instead, the `dataRead()` method of the Python gto.Reader class is handed pre-allocated Python objects containing the data. + + +| | | | +| --- | --- | --- | +| *status* `gto.Reader` *(mode)* | Create a new gto.Reader instance. Possible values for *mode* : +**gto.Reader.NONE** +The reader will be used in its standard *streaming* mode. The reader will attempt to read all the data in the file. This is the default value (or 0). +**gto.Reader.HEADERONLY** +The reader will stop once it has read the header sections of the GTO file. +**gto.Reader.RANDOMACCESS** +The reader will read the header sections but not the data, however, it will initialize for use of the `gto.Reader.accessObject()` method. +**gto.Reader.BINARYONLY** +The reader will only accept binary GTO files. +**gto.Reader.TEXTONLY** +The reader will only accept text GTO files. | [Constructor] | +| `gto.Reader.open` *(filename)* | Opens and reads the GTO file *filename* . The function will raise a Python exception if the file cannot be opened. | [Method] | +| *wants* `gto.Reader.object` *(name, `protocol` , `protocolVersion` , `objectInfo` )* | This function is called by the base class to declare an object in the GTO file. The return value *wants* should evaluate to True or False, indicating whether or not the base class should read the object data. *name* and *protocol* are strings declaring name and protocol of the object, *protocolVersion* is an integer. *objectInfo* is an instance of a generic class which contains the same information as the Gto::ObjectInfo C++ struct. | [Method] | +| *wants* `gto.Reader.component` *(name, `interpretation` , `componentInfo` )* | This function is called by the base class to declare a component in the GTO file. The return value *wants* should evaluate to True or False, indicating whether or not the base class should read the component data. *name* is a string declaring the component name. *componentInfo* is an instance of a generic class which contains the same information as the Gto::ComponentInfo C++ struct. | [Method] | +| *wants* `gto.Reader.property` *(name, `interpretation` , `propertyInfo` )* | This function is called by the base class to declare a property in the GTO file. The return value *wants* should evaluate to True or False, indicating whether or not the base class should read the property data. *name* is a string declaring the full property name. *propertyInfo* is an instance of a generic class which contains the same information as the Gto::PropertyInfo C++ struct. | [Method] | +| `gto.Reader.dataRead` *(name, data, propertyInfo)* | If a property has been requested, the dataRead() function will eventually be called by the base class with the actual data in the file. The *name* is the name of a property, *data* is a tuple containing the property data, *propertyInfo* is an instance of a generic class which contains the same information as the Gto::PropertyInfo C++ struct. | [Method] | +| `gto.Reader.stringFromID` *(id)* | Returns the stringTable entry for the given string table id. Since the Python gto module returns strings directly, it is unlikely that you’ll need to use this. | [Method] | +| `gto.Reader.stringTable ()` | Returns the entire stringTable as a list of strings. | [Method] | +| `gto.Reader.isSwapped ()` | Returns True if the file on disk is not in the machine’s native byte order. | [Method] | +| `gto.Reader.objects ()` | Returns a list of the gto.ObjectInfo instances for all the objects in the file. This method is only available if the file was opened with gto.Reader.RANDOMACCESS. Usable at any time after the constructor is called. | [Method] | +| `gto.Reader.components ()` | Returns a list of the gto.ComponentInfo instances for all the components in the file. This method is only available if the file was opened with gto.Reader.RANDOMACCESS. Usable at any time after the constructor is called. | [Method] | +| `gto.Reader.properties ()` | Returns a list of the gto.PropertyInfo instances for all the properties in the file. This method is only available if the file was opened with gto.Reader.RANDOMACCESS. Usable at any time after the constructor is called. | [Method] | +| `gto.Reader.accessObject` *(objInfo)* | Given an instance of gto.ObjectInfo (obtained via gto.Reader.objects(), gto.Reader.components(), or gto.Reader.properties()), tells the reader to access that object directly. This will cause the gto.Reader.object(), gto.Reader.component(), and gto.Reader.dataRead() methods to be called with the information from the given object. | [Method] | + +### gto.Writer + + + +| | | | +| --- | --- | --- | +| `gto.Writer ( )` | Creates a new writer instance, no arguments needed. | [Constructor] | +| `gto.Writer.open` *(filename, mode)* | Open the file. The Writer will attempt to open file *filename* . If the file is not writable for whatever reason, the function will raise a Python exception. The *mode* argument can be `BINARYGTO` , `COMPRESSEDGTO` (the default) or `TEXTGTO` . | [Method] | +| `gto.Writer.close ( )` | Close the file and clean up temporary data. Because of Python’s garbage-collection, you can never be sure when a class’s destructor will be called. Therefore, it is *highly* recommended that you call this method to close your file when it’s done writing. You have been warned. | [Method] | +| `gto.Writer.beginObject` *(name, protocol, version)* | Declares an object. Its components and properties must be declared before endObject() is called. The *name* is the name of the object as it will appear in the gto file. The *protocol* is the protocol string indicating how the object data will be interpreted and the *version* number indicates the protocol version. The Writer class does not verify that the data output conforms to the protocol. | [Method] | +| `gto.Writer.beginComponent` *(name, interpretation, transposed)* | Declares a component. The component properties must be declared before a call to endComponent(). The *name* is the name of the component as it will appear in the gto file. The *transposed* flag is optional and indicates whether or not the component property data should be output transposed or one property at a time (the default). | [Method] | +| `gto.Writer.property` *(name, type, numElements, partsPerElement, interpretation)* | Declare a property. The *name* is the name of the property as it appears in the gto file. The *type* is one of gto.DOUBLE, gto.FLOAT, gto.INT, gto.STRING, gto.BYTE, gto.HALF ( *Not implemented* ), or gto.SHORT. *numElements* indicates the number of elements of size *partsPerElement* that will be in the property data. So for example, if the property is declared as a gto.FLOAT of with *partsPerElement* of 3 and there 10 of them, then the writer will expect a sequence of 30 floats when the propertyData is finally passed to it. | [Method] | +| `gto.Writer.endComponent ()` | Closes the declaration of a component started by beginComponent(). | [Method] | +| `gto.Writer.endObject ()` | Closes the declaration of an object started by beginObject(). | [Method] | +| `gto.Writer.intern` *(string)* | Declares a string to the Writer for inclusion in the file string table. When writing properties of type gto.String, its necessary to call this function for each string in the property data before the beginData() is called. The Python version of intern() can accept individual strings, as well as lists or tuples of strings. | [Method] | +| `int gto.Writer.lookup` *(string)* | Retrieve the identifier of the previously interned string. Valid only after beginData() has been called. | [Method] | +| `gto.Writer.beginData ()` | Begins data declaration to the Writer class. Only calls to lookup(), propertyData(), and endData() are legal after beginData() is called. | [Method] | +| `gto.Writer.propertyData` *(data)* | The propertyData() function must get exactly *one* parameter. That parameter can be any of the following: +• A single int, float, string, etc. +• An instance of mat3, vec3, mat4, vec4, or quat (http://cgkit.sourceforge. net / ). DO NOT explicitly cast mat3 or mat4 into a tuple or list: `tuple(mat4(1))` . It will be silently transposed (a bug in the cgtypes code?). ADDING it to a tuple or list is fine: `(mat4(1),)` +• A tuple or list of any combination of the above that makes sense. + +Tuples and lists are flattened out before they are written. As long as the number of atoms is equal to size x width, it’ll work. Calls to propertyData() must appear in the same order as declared with the property() method. | [Method] | +| `void gto.Writer.endData ()` | Closes the definition of data started by beginData() and finishes writing the gto file. Does *not* actually close the file–use the close() method for that. | [Method] | + +### Classes used by gto.Reader + + +These classes will contain the actual strings rather than string table IDs. + + +| | | | +| --- | --- | --- | +| `gto.ObjectInfo` | This class emulates the Gto::ObjectInfo struct from the C++ Gto library. It is passed by the Python gto.Reader class to your derived `object()` method. The only methods implemented are `___getattr__` and `__repr__` . Available attributes are: +• `name` : String +• `protocolName` : String +• `protocolVersion` : Integer +• `numComponents` : Integer +• `pad` : Integer | [Class] | +| `gto.ComponentInfo` | This class emulates the Gto::ComponentInfo struct from the C++ Gto library. It is passed by the Python gto.Reader class to your derived `component()` method. The only methods implemented are `__getattr__` and `__repr__` . Available attributes are: +• `name` : String +• `numProperties` : Integer +• `flags` : Integer +• `interpretation` : String +• `pad` : Integer +• `object` : Instance of gto.ObjectInfo | [Class] | +| `gto.PropertyInfo` | This class emulates the Gto::PropertyInfo struct from the C++ Gto library. It is passed by the Python gto.Reader class to your derived `property()` and `dataRead()` methods. The only methods implemented are `__getattr__` and `__repr__` . Available attributes are: +• `name` : String +• `size` : Integer +• `type` : Integer +• `width` : Integer +• `interpretation` : String +• `pad` : Integer +• `component` : Instance of gto.ComponentInfo | [Class] | + +## Utilities + + +### The `gtoinfo` Utility + + +Usage: `gtoinfo [OPTIONS] infile.gto` + +Options: + + +| | | +| --- | --- | +| `-a/-all` | Output property data and header. | +| `-d/--dump` | Output property data (no header data is emitted). | +| `-l/--line` | Output property data one item per line. Can be used with either `-d` or `-s` . | +| `-h/--header` | Output header data. | +| `-s/--strings` | Output sting table data. | +| `-n/--numeric-strings` | Output sting data as the raw string id instead of the string itself. | +| `-i/--interpretation-strings` | Output interpretation string data for components and properties if it exists. | +| `-r/--readall` | Force reading of the enitre gto file even if only the header is being output. | +| `-f/--filter expression` | Only output information for properties who’s long name (object.component.propname) matches the shell-like *expression* . Section gtofilter for examples of filter expressions. This option is similar to `gtofilter --include` option. | +| `--help` | Output usage message. | + +`gtoinfo` outputs the part of all of the contents of a gto file in human readable form. Its invaluable for debugging or just getting a quick understanding of what a gto file contains. + +### The `gtofilter` Utility + + +Usage: ‘ `gtofilter [OPTIONS] -o` *out.gto in.gto* ’ + +Options: + + +| | | +| --- | --- | +| `-v` | Set verbose output. Whenever a pattern matches gtofilter will inform you. | +| `-ee/--exclude` | Regular expression which will be used to exclude properties. | +| `-ie/--include` | Regular expression which will be used to include properties. | +| `-regex` | Use POSIX regular expression syntax. | +| `-glob` | Use shell-like regular expression (fnmatch). This is the default. | +| `-t` | Output text GTO file. | +| `-nc` | Output uncompressed binary GTO file. | +| `-o` *out.gto* | Output .gto file | + +`gtofilter` can be used to remove objects, components, and properties from a gto file. You supply an include shell-like expression and/or an exclude shell-like expression. (The pattern matching is done using the fnmatch() function—see the main page for details.) + +The patterns match each full property name. So for example a cube might have these properties: + +``` +cube.points.position +cube.elements.type +cube.elements.size +cube.indices.vertex +cube.indices.st +cube.indices.normal +cube.normals.normal +cube.mappings.st +cube.smoothing.method +cube.object.globalMatrix +cube.object.parent +``` + +Using the `--exclude` option, you can remove the object component by doing this: + +``` +gtofilter --exclude "*.object.*" -o out.gto cube.gto +``` + +Or if you wanted to pass through only the positions: + +``` +gtofilter --include "*.*.positions" -o out.gto cube.gto + -or- +gtofilter --include "*positions" -o out.gto cube.gto +``` + +### The `gtomerge` Utility + + +Usage: ‘ `gtomerge` *-o outfile.gto infile1.gto infile2.gto …* ’ + +Options: + +| | | +| --- | --- | +| `-o outfile.gto` | The resulting merged file to output. | +| `-t` | Ouput text GTO file. | +| `-nc` | Ouput uncompressed binary GTO file. | + +`gtomerge` takes a number of .gto input files and merges them into a single output .gto file. This is done by first creating output geometry that is identical to the first input file and then adding only those properties that are not already defined from subsequent gto files. The order of input files determines what will be in the final output file. + +For **difference** files, you can use gtomerge to reconstruct a final file like this: + +`gtomerge -o out.gto difference.gto reference.gto` + +### The `gto2obj` Utility + + +Usage: ‘ `gto2obj [OPTIONS]` *infile outfile* ’ + +Options: + + +| | | +| --- | --- | +| `-o NAME` | When outputing GTO files, the name of an object in the GTO file to output. If not specified, the translator will output the first polygon, or subdivision surface it finds. | +| `-c` | When outputing GTO files, this option will force the protocol to be “catmullclark”. | +| `-l` | When outputing GTO files, this option will force the protocol to be “loop”. | +| `-t` | Ouput text GTO file. | +| `-nc` | Output uncompressed binary GTO file. | + +`gto2obj` takes either an input GTO file or Wavefront .obj file and outputs the other file type. + +``` +gto2obj in.obj out.gto +gto2obj in.gto out.obj +gto2obj -c in.obj out.gto ## output obj as subdivision surface +``` + +### The `gtoimage` Utility + + +Usage: ‘ `gtoimage` infile outfile’ + +| | | +| --- | --- | +| `-t` | Ouput text GTO file. | +| `-nc` | Ouput uncompressed binary GTO file. | + +`gtoimage` reads a TIFF file and converts it into a GTO file containing one image object. 32 bit floating point images, 16 bit and 8 bit integral images are directly converted. `gtoimage` expects the image to be two dimensional with three or four channels where the fourth channel is an optional alpha value. The output object conforms to the **image** protocol. See Section Image. + +You can use `gtomerge` to merge the image object into another GTO file. See Section gtomerge. + +It is highly recommend that the resulting output GTO file be written with compression or gzipped to reduce its size. Gzipped GTO files can be read directly by the supplied readers. + +### The `RiGtoRibOut` Utility + + +The `RiGtoRibOut` command is useful for: + +* It can be used as a debugging tool for the RiGtoPlugin RenderMan plugin. +* It can be used as a drop-in replacement for RiGtoPlugin, for RIB renderers that do not support `Procedural DynamicLoad` , but that *do* support `Procedural RunProgram` . Note that this is substantially slower than using RiGtoPlugin, as all data needs to be translated to ASCII and back. It does have the one space-saving advantage of not needing to save ASCII RIB on disk. +* It could be used to generate RIB files that are read with `ReadArchive` . This is not recommended, as it negates all the advantages of using GTO in the first place. But if nothing else works, this should. + +The command-line parameters are the same as the *CONFIG STRING* for RiGtoPlugin. See Section RiGtoPlugin. + +### The `gtoIO.so` Maya Plug-In + + +The Maya plugin comes in two parts: the C++ plugin which implements a Maya scene translator and an accompanying MEL script which implements the user interface. + +The plugin handles export of NURBS surfaces (but not trim curves), polygonal geometry (which can be written as sub-division surfaces), and generic transforms. A Maya particle export tool is in the works. Additional user defined attributes can be emitted into the GTO file. + +The plugin can import everything that it exports and also particle GTO files generated by other applications. + +#### BUGS + +The internal performance of Maya has changed between the 4.x and 5.0 versions. In Maya 5.0, the Maya API is *extremely* slow when importing polygonal normals. Importing of normals is turned off in Maya 5.0. + +### The RiGtoPlugin RenderMan plugin + + +Here you will find information on using the GTO RenderMan plugin. The documentation is complete enough to get started with, but should be considered a work in progress. + +#### RIB Instantiation + +The plugin is instantiated in a RIB Stream using the standard DynamicLoad procedural mechanism, like so: + +``` +Procedural "DynamicLoad" [ "RiGtoPlugin.so" "CONFIG_STRING" ] [ Bounding Box ] +``` + +If a bounding box is not known, the infinite box may be used: + +``` +Procedural "DynamicLoad" [ "RiGtoPlugin.so" "CONFIG_STRING" ] [ -1e6 1e6 -1e6 1e6 -1e6 +``` + +#### Config String Syntax + +The configuration string passed into RiGtoPlugin consists of a variable number of space-separated tokens. They are, in order: + +1. Reference Pose GTO File Name +2. Shutter Open GTO File Name (optional) +3. Shutter Close GTO File Name (optional) +4. Primary On List (optional) +5. Primary Off List (optional) +6. Secondary On List (optional) +7. Secondary Off List (optional) + +As shown, the only necessary element is the reference GTO file. For objects which do not have movement and do not require on lists or off lists, this is completely sufficient. + +The logic behind the geometry instantiation mechanism is as follows: + +* Read Reference GTO file The plugin reads all of the geometry in the reference GTO file. As a starting point, the shutter open and close geometry is set equal to the reference geometry. +* If requested, read Shutter Open GTO file The plugin then reads any geometry from the Shutter Open file that matches the name and geometry type of geometry that has already been read from the reference file—this geometry is stored as both the shutter open AND close geometry. +* If requested, read Shutter Close GTO file The plugin then reads any geometry from the Shutter Close file that matches the name and geometry type of geometry that has already been read from the reference file—this geometry is stored as the shutter close geometry +* Instantiate Geometry: For any piece of geometry that appears in BOTH on-lists and does not appear in EITHER off-lists, the plugin calls the appropriate RIB functions to create the requested geometry. + +#### On-List/Off-List Syntax + +The syntax of the on-lists and off-lists is as follows: + +`NULL` is a special on-list/off-list which is interpreted as *all on* or *none off* . + +Otherwise, the on-lists and off-lists are essentially shell-like regular expressions. The following rules apply: + +* The `*` character matches any number of characters +* The `?` character matches any single of character +* Bracket expressions `[]` are supported. (See `man 7 regex` ) +* Multiple patterns can be strung together with the `|` character. +* The pattern must match the *whole* object name. Thus, the pattern “ `*Sphere1` ” will match the object `nurbsSphere1` but *not* `nurbsSphere1Shape` . This is a very common “gotcha”. + +As an example, suppose you wanted to turn off all of the geometry named `LeftLeg*Shape*` and `RightLeg*Shape*` in a render—you would create an off-list that looked like: + +``` +"LeftLeg*Shape*|RightLeg*Shape*" +``` + +#### Cache Management + +By default, RiGtoPlugin maintains an internal cache of all of the file sets it has read. The cache is keyed off of Ref-Open-Close filename triplets. The reason for this is to facilitate easy material assignment, which will be discussed in greater detail below in the “Strategy” section. + +In situations where memory is precious and the renderer needs as much memory as it can get, it may be advantageous to force RiGtoPlugin to discard its cached file sets. There is special syntax to facilitate this. + +* To erase everything in RiGtoPlugin’s cache: + +``` + Procedural "DynamicLoad" [ "RiGtoPlugin.so" "__FLUSH__" ] [ Bounding Box ] +``` + +* To erase the cache associated with a given file triplet: (Using REF.gto, OPEN.gto and CLOSE.gto as standins for whatever files were actually passed in) + +``` + Procedural "DynamicLoad" [ "RiGtoPlugin.so" "REF.gto OPEN.gto CLOSE.gto __FLUSH__" ] [ Bounding Box ] +``` + +There is also an environment variable, `TWK_RI_GTO_NO_CACHE` , which if defined and set to anything other than “0”, “FALSE”, “False” or “false”, will cause caching to be turned off entirely. + +#### Environment Variables + + +| | | | +| --- | --- | --- | +| `TWK_RI_GTO_NO_SUBDS` | If this environment variable is defined and set to anything except “0”, “FALSE”, “False”, or “false”, RiGtoPlugin will treat all catmull-clark subdivision surfaces read from a GTO file as polygons instead. | [Environment Variable] | +| `TWK_RI_GTO_NO_CACHE` | If this environment variable is defined and set to anything except “0”, “FALSE”, “False”, or “false”, RiGtoPlugin will turn off all caching of geometry data to save memory. | [Environment Variable] | + +#### Usage Strategy + +The RiGtoPlugin was designed with a particular data structure in mind. Used ideally, there would be a GTO file consisting of all of the geometry corresponding to a particular high-level creature or set in the scene. All of the surfaces corresponding to a hippo or a giraffe or a cyborg-monkey would be in a single GTO file. The animation data for this geometry would be contained in light-weight GTO files that contain only points that have moved and transformation matrices that have moved. The RiGtoPlugin only reads points and matrices from the Shutter-Open and Shutter-Close file, facilitating very light-weight “difference” files for animation data. + +Because all of the geometry in a creature will have different materials assigned to it, on-lists and off-lists can be used to separate out only the geometry that shares a particular material. + +Suppose we have a creature consisting of many surfaces but only three different materials—skinMtl, eyeMtl and hairMtl. The parts of the model have been named intelligently (for this example) such that the skin parts all have names like Skin\*Shape\*, the eye parts all have names like Eye\*Shape\*, and the hair parts all have names like Hair\*Shape\*. Then, the RIB for declaring this creature with material assignments might look like this: + +``` +AttributeBegin +Surface "skinShader" [ shader param settings ] +Procedural "DynamicLoad" [ "RiGtoPlugin.so" "thing.ref.gtothing.0013.open.gto thing.0 +AttributeEnd + +AttributeBegin +Surface "hairShader" [ shader param settings ] +Procedural "DynamicLoad" [ "RiGtoPlugin.so" "thing.ref.gtothing.0013.open.gto thing.0013.close.gto Hair*Shape*" ][-1e6 1e6 -1e6 1e6 -1e6 1e6] +AttributeEnd + +AttributeBegin +Surface "eyeShader" [ shader param settings ] +Procedural "DynamicLoad" [ "RiGtoPlugin.so" "thing.ref.gtothing.0013.open.gto thing.0013.close.gto Eye*Shape*" ][-1e6 1e6 -1e6 1e6 -1e6 1e6] +AttributeEnd +``` + +Because of RiGtoPlugin’s cache mechanism, the geometry associated with the file-set thing.\*.gto is only read and interpreted one time—the on-lists control which parts of the geometry are instantiated at which times. To nuke the cache of these files (if memory is important), you would use the syntax: + +``` +Procedural "DynamicLoad" [ "RiGtoPlugin.so" "thing.ref.gtothing.0013.open.gto thing.0 +``` + +#### Miscellaneous RenderMan Stuff + +RiGtoPlugin stores some useful data in attributes that can be used by shaders if desired. + + +| | | | +| --- | --- | --- | +| `Pref` | On ALL geometry RiGtoPlugin creates “varying point Pref” as part of its geometry declaration. This data can be accessed by simply putting “varying point Pref” in your shader parameters. The position of the model in the reference GTO file is always used for this parameter value. | [Shader parameter] | +| `Name` | RiGtoPlugin always places the name of the geometry, as retrieved from the GTO file, in an attribute that may be queried. It is exactly as if the following line of RIB were declared before the geometry were instantiated: +`Attribute “identifier” “name” [“whatever my name is”]` | [RIB Attribute] | +| `RefToWorld` *matrix* | RiGtoPlugin places the transformation matrix *objectToWorld* from the reference model into a user attribute called `refToWorld` . To prevent this attribute from being munged by the current transformation matrix, it is cast as a float[16] instead of a matrix. It is equivalent to this line of RIB: +`Attribute “user” “float refToWorld[16]” [ the matrix values ]` | [RIB Attribute] | diff --git a/doc/rv-manuals/rv-luts.md b/doc/rv-manuals/rv-luts.md new file mode 100644 index 000000000..6469f628c --- /dev/null +++ b/doc/rv-manuals/rv-luts.md @@ -0,0 +1,93 @@ +# Using LUTs in Open RV + +Look up tables (LUTs) are useful for approximating complicated color transforms, especially those which have no known precise mathematical representation. RV provides four points in its color pipeline where LUTs can be applied: just after reading the file and before caching directly after the cache (file LUT), just before display transforms (look LUT), and as one of the display transforms (display LUT). The first three are per-source while there is only a single display LUT for each RV session. + +Each of the LUTs can be either a channel LUT or a 3D LUT (the difference is explained below. In the case of a 3D LUT there can also be an additional channel pre-LUT which can be used to shape the data. Both types of LUT are preceded by an input matrix which can scale high dynamic range data into the range of the LUT input (which is the range [0,1]). The values the LUT produces can be outside of the [0,1] range. This makes it possible for any of the LUTs to transform colors outside of the typical [0,1] range on both input and output. + +Internally, RV will store the LUT as either half precision floating point or 16 bit integral. Not all hardware is capable of processing LUTs stored as floating point (esp. the 3D LUTs) so if you notice banding or noisy output when using floating point LUT storage, you may have better luck with the 16 bit integral representation. If RV can determine whether the floating point LUTs are usable itself it will default to whatever is appropriate. + +When applied in hardware, the LUTs are interpolated when a value is not exactly represented in the LUT. This is usually more of an issue with 3D LUTs than channel LUTs since they have fewer samples per dimension. When interpolating between sample values, RV uses linear interpolation for channel LUTs and tri-linear interpolation for 3D LUTs. + +There are a number of ways to create a LUT. For film look simulation, it’s often necessary to have special hardware to measure and compare film recorder output. Alternately, you use a lightbox; and assuming you have a well calibrated neutral monitor, “eyeball” the LUT by comparing the film to the monitor. + +RV has two different algorithms for applying the LUTs on the GPU: using floating point or fixed-point integer textures. Not all cards are equally capable with 3D LUTs and floating point. If RV detects that the card probably can’t do a good job with the floating point hardware it will switch to a fixed-point representation using 16 bit integer LUTs. Sometimes even though the driver reports that the LUTs can be floating point, you will see banding in the final images. If that occurs, try forcing the use fixed-point LUTs by turning off the Floating Point 3D LUTs item on the Rendering tab of the preferences. The fixed point LUT algorithm will perform just as well as floating point in 99% of normal use cases. + +## Channel (1D) versus 3D LUTs + +A channel LUT (also called a 1D LUT) has three independent look-up tables: one each for the R G and B channels. The alpha channel is not affected by the channel LUT. Channel LUTs may be very high resolution with up to 4096 samples. Each entry in the channel LUT maps an input channel value to an output channel value. The input values are in the [0,1] range, but the output values are unbounded. + +Channel LUTs differ from 3D LUTs in one critical way: they can only modify channel values independently of one another. In other words, e.g., the output value of the red channel can only be a result of the incoming red value. In a 3D LUT, this is not the case: the output value of the red channel can be dependent on any or all of the input red, green, and blue values. This is sometimes called channel cross-talk. + +The other important difference between channel and 3D LUTs is the number of samples. Channel LUTs are one dimensional and therefor consume much less memory than 3D LUTs. Because of this, channel LUTs can have more samples per-channel than 3D LUTs. + +The implication of all this is that channel LUTs are useful for representing functions like gamma or log to linear which don’t involve cross-talk between channels whereas 3D LUTs are good for representing more general color transforms and + +3D LUTs can be very memory intensive. A 64 × 64 × 64 LUT requires 64 3 × 4 bytes of data (3Mb). You can quickly run out of memory for your image on the graphics card by making the 3D LUT too big (e.g. 128 × 128 × 128, this will slow RV down). RV does not require the 3D LUT to have the same resolution in each dimension. You may find that a particular LUT is smooth or nearly linear in one or more dimensions. In that case you can use a lower resolution in those dimensions. + +Some graphics cards have resolution issues with 3D textures which can cause loss of precision when RV’s 3D LUT feature is enabled. On older NVidia cards and ATI cards in general, 3D textures may be limited to non-floating point color representations. Precision loss when using a LUT can be exacerbated by applying display gamma on these cards. To minimize precision loss on those types of cards, bake monitor gamma and/log-lin conversion directly into the display LUT. With newer GPUs this is not as much of an issue. + +## Input Matrix and Pre-LUT + +For HDR applications, the incoming data needs to be rescaled and possibly shaped. RV has two separate components which do this: the LUT input matrix and a channel pre-LUT. The input matrix is a general 4 × 4 matrix. For HDR pixels, the matrix is used to scale the incoming pixel to range [0, 1]. The pre-LUT, the channel LUT, and the 3D LUT all take inputs in that range. The figure below shows a diagram of the channel and 3D LUT components and their input and output ranges. + +The pre-LUT is identical to the channel LUT in implementation. It maps single channel values to new values. Unlike the general channel LUT, the pre-LUT must always map values in the [0, 1] range into the same range. The purpose of the pre-LUT is to condition the data before it’s transformed by the 3D LUT. + +For example, it may make sense for 3D LUT input values to be in a non-linear space – like log space. If the incoming pixels are linear they need to be transformed to log before the 3D LUT is applied. By using a relatively high resolution pre-LUT the data can be transformed into that space without precision loss. + +![lut_pipeline_diagram.png](../images/rv-luts-lut-pipeline-diagram-01.png) + +*3D and Channel LUT Components* + +## The Pre-Cache LUT + +The first LUT that the pixels can be transformed by is the pre-cache LUT. This LUT has the same parameters and features as the other LUTs, but it is applied before the cache. The pre-cache LUT is currently applied by the CPU (not on the GPU) whereas the file, look, and display LUTs are all used by the graphics hardware directly. For this reason the pre-cache LUT is slightly slower than the others. + +The pre-cache LUT is useful when a special caching format is desired. For example by using the pre-cache LUT and the color bit depth formatting, you can have RV convert linear OpenEXR data into 8 bit integer format in log space. By using RV’s log to linear conversion on the cached 8 bit data you can effectively store high dynamic range data (albeit limited range) and get double the number of frames into the cache. Many encoding schemes are possible by coupling a custom pre-cache LUT, change of bit depth, and the hardware file LUT to decode on the card. + +## LUT File Formats + +| Extension | Type | 1D | 3D | PreLUT | Float | Input | Output | +| --- | --- | --- | --- | --- | --- | --- | --- | +| csp | Rising Sun Research | • | • | • | • | [ - ∞ , ∞] | [ - ∞ , ∞] | +| rv3dlut | RV 3D | | • | | • | [0 , 1] | [ - ∞ , ∞] | +| rvchlut | RV Channel | • | | | • | [0 , 1] | [ - ∞ , ∞] | +| 3dl | Lustre | | • | | | [0 , 1] | [0 , 1] | +| cube | IRIDAS | | • | | • | [0 , 1] | [ - ∞ , ∞] | +| any | Shake | • | | | • | [0 , 1] | [ - ∞ , ∞] | + +*LUT Formats (as Supported in RV)* + +RV supports several of the common LUT file formats. Unfortunately, not all LUT formats are equally capable and some of them are not terribly well defined. In most cases, you need to know the intended use of a particular LUT file. For example, it doesn’t make sense to apply a LUT file which expects the incoming pixels to be in Kodak Log space to pixels from an EXR file (which is typically in a linear space). Often there is no way to tell the intended usage of a LUT file other than its file name or possibly comments in the file itself. Most formats do not have a public mechanism to indicate the usage to an application. + +To complicate matters, many LUT files are intended to map directly from the pixels in a particular file format directly to your monitor. When using these types of LUTs in RV you should be aware than making any changes to the color using RV’s color corrections or display corrections will not produce expected results (because you are operation on pixels in the color space appropriate for the display, rather than in linear space). + +One of the more common types of LUT files you are likely to come across is one which maps Kodak Log space to sRGB display space. The file name of that kind of LUT might be log2sRGB or something similar. A variation on that same type of LUT might include an additional component that simulates the look of the pixels when projected from a particular type of film stock. Strictly speaking, you do not need to use log to sRGB LUTs with RV because it implements these functions itself (and they are exact, not approximated). So ideally, if you require film output simulation you have a LUT which only does that one transform. Of course this is often not the case; the world of LUT formats is a complicated one. + + +### RSR LUT Format + +Currently, the best LUT format for use with RV is the .csp format. This format handles high dynamic range input and output as well as non-linear and linear pre-LUTs. It maps most closely to RV’s internal LUT functions. + +There is one type of .csp file which RV does not handle: a channel LUT with a non-linear pre-LUT. This is probably a very rare beast since an equivalent 1D LUT can be created with a linear pre-LUT. An error will occur if you attempt to use a channel LUT with a non-linear pre-LUT. + +When RV reads a pre-LUT from this file format and it can determine that the pre-LUT is linear, it will convert the pre-LUT into a matrix and apply it as the LUT input matrix. In that case the non-linear channel pre-LUT is not needed. If the pre-LUT is non-linear (in any channel) RV will construct a channel LUT which is used just before the 3D LUT. Input values in the .csp pre-LUT are normalized and the scaling is then moved to the input matrix. Using a matrix when possible frees up resources for other LUTs and images in the GPU. Any pre-LUT in a .csp file with only two values is by definition a linear pre-LUT. + +``` + CSPLUTV100 +3D + +2 ^\label{preLUTStart}^ +0 13.5 +0 1 +2 +0 13.5 +0 1 +2 +0 13.5 +0 1 ^\label{preLUTEnd}^ + +... +``` +In the above listing, lines preLUTStart to preLUTEnd are linear pre-LUT values. In this case the pre-LUT values are mapping values int the range [0,13.5] down to [0,1] for processing by a 3D LUT (which is not shown). For a summary of the RSR .csp format see Appendix [G](../rv-manuals/rv-user-manual/rv-user-manual-chapter-g.md) . + +For the most part, it’s not necessary to know the distinction between a linear and non-linear pre-LUT in the file. However, the behavior of the pre-LUT outside the bounds of its largest and smallest input values will be different for linear pre-LUTs. Since the pre-LUT is represented as a matrix, it will not clamp values outside the specified range. Non-linear pre-LUTs will clamp values. \ No newline at end of file diff --git a/doc/rv-manuals/rv-media-multi-representation.md b/doc/rv-manuals/rv-media-multi-representation.md new file mode 100644 index 000000000..cc474fe6b --- /dev/null +++ b/doc/rv-manuals/rv-media-multi-representation.md @@ -0,0 +1,155 @@ +# Using Multiple Media Representations in Open RV + +## Introduction + +Multiple Media Representations makes it easy for you to swap between media representations in RV. + +Multiple Media Representation (MMR) is the implementation of the new OTIO feature named *Media Multiple References*. With RV MMR, you easily switch between the different source media representations referenced by the timeline. + +MMR allows you to switch between multiple source media representations since these representations are expressed in the RV graph. + +## Swap between source media representations + +In RV, the Multiple Media Representations feature allows you to swap between source media representation for any given frame. + +To swap source media: + +1. Click the Swap drop-down menu. + +1. Select the source media to display. + + ![MMR Dropdown menu](../images/rv-manuals-media-multi-representation-dropdown-menu.png) + +You can only select available source media. Any dimmed option is unavailable to you. + +By default, there are three possible options. + +- **Frames**: for Image sequences, usually OpenEXR. + +- **Movie**: for Movie file. + +- **Streaming**: for Media streaming. + +The resolution and type of media your actually viewing are displayed on the toolbar, next to the Swap Media drop-down. + +![MMR Media Info](../images/rv-manuals-media-multi-representation-media-info.png) + +> Important: During playback, if you lose access to the source media, RV falls back to the next available source media, in this order: Frames > Movie > Streaming(S3). + +### How do I know my clip has multiple representations + +If the Swap menu appears in the playback area, your clip has multiple media representations. If the menu is not there, the media you see is the only media available and you cannot swap sources. + +![MMR menu present](../images/rv-manuals-media-multi-representation-swap-menu.png) + +## Exporting MMR clips to OTIO + +When you export a clip to OTIO, the currently selected media representation is set as the active reference in the OTIO file. This requires exporting using a version of OTIO that supports *Media Multiple References*, which means OTIO 0.15 or later. If your version of OTIO does not support MMR, only the current media representation is exported. + +For technical information on MMR in OTIO, see [Media Multi-Reference Support](../rv-packages/rv-otio-reader.md#media-multi-reference-support). + +## Multiple Media Representations: Technical Details + +### Setting Open RV Fall Back Order on Missing Source Media + +During playback, if RV loses access to the source media selected by the user, RV falls back to the next available source media. It follows this order: Frames > Movie > Streaming(S3). + +You can implement your own fallback order by overloading the package `RV Multiple Source Media Representation Management`. See the package notes for more details. + +### Multiple Media Representations Underlying Schema + +When *not* using multiple media representations, the RV session is identical to that of previous versions. + +![no switch group](../images/rv-manuals-media-multi-representation-scheme-no-switch-group.png) + +When using multiple media representations either with the new `addSourceMediaRep()` command or the new `+mediaRepName` option used together with `addSources()`, `addSourceVerbose()`, or `addSourcesVerbose()`, the `SwitchGroup` gets inserted to connect the different source media representations. + +![with switch group](../images/rv-manuals-media-multi-representation-scheme-with-switch-group.png) + +The `SwitchIPNode` is available in older versions of RV. This means that older versions of RV are able to load a new RV session containing multiple media representations. + +### New commands + +#### string addSourceMediaRep(string sourceNode or "last" for last created source node, string mediaRepName, string[] mediaRepPathsAndOptions=[], string tag=[]) + +Add a media representation to an existing source specified by sourceNode with an optional tag. + +If the source media representation already exists, `addSourceMediaRep()` throws the following error: + +```sh +ERROR: Source media representation name already exists: [media representation name] +``` + +**Returns:** + +`string sourceNode` - returns the name of the source node created + +**Params:** + +`string sourceNode` - The source node for which to add the media representation or "last" for the last created source. + +`string mediaRepName` - The name of the media representation to add. + +`string[] mediaRepPathsAndOptions` - The paths or URLs of the media representation to add and additional options of the media representation to add, if any. + +`string tag` - An optional tag can be provided which is passed to the generated internal events. The tag can indicate the context in which the `addMediaRep()` call is occurring (for example, drag, drop, etc.) It's also possible to add a source media representation in RV without calling `addSourceMediaRep()`: specify the `mediaRepName` and `mediaRepSource` options to the `addSources()`, `addSourceVerbose()`, or `addSourcesVerbose()` commands. + +**Examples:** + +```js +rv.commands.addSourceMediaRep("sourceGroup000000_source", "Movie", ["hippo_numbered.mov"]) +rv.commands.addSourceMediaRep("sourceGroup000000_source", "Streaming", ["https://www.acme.com/file_serve/version/7966/mp4"]) +rv.commands.addSourceMediaRep("sourceGroup000000_source", "Frames", ["hippo_numbered.195-215#.jpg"]) +rv.commands.addSourceMediaRep("sourceGroup000000_source", "Movie", ["left.mov", "right.mov"]) +rv.commands.addSourceMediaRep("sourceGroup000000_source", "Movie", ["hippo_numbered.mov", "+rs", "194", "+pa", "0.0", "+in", "195", "+out", "215"]) +``` + +`string tag` exemple: + +```js +rv.commands.addSourcesVerbose(["image_sequence.195-215#.jpg", "+mediaRepName", "Frames", "+mediaRepSource", "last"]) +``` + +#### void setActiveSourceMediaRep(string sourceNodeOrSwitchNode, string mediaRepName) + +Set the active input of the Switch node specified or the ones associated with the specified source node to the given media representation specified by name. + +When `sourceNode` is an empty string "", then the media in the first Switch node found at the current frame is swapped with the media representation specified by name. + +When `sourceNode` is "all", then the media in all the Switch nodes created with the `rvc.addSourceMediaRep()` command is swapped with the media representation specified by name. + +> Note: If the Source Groups do not contain the specified media representation, then `setActiveSourceMediaRep()` throws an error unless `sourceNodeOrSwitchNode` is "" or "all". + +- Set the active media representation for the current source to *Streaming*: + + ```js + rv.commands.setActiveSourceMediaRep("", "Streaming") + ``` + +- Set the active media representation for all the sources to *Streaming*: + + ```js + rv.commands.setActiveSourceMediaRep("all", "Streaming") + ``` + +- Set the active media representation for the Switch nodes associated with the specified source to *Streaming*: + + ```js + rv.commands.setActiveSourceMediaRep("sourceGroup000000_source", "Streaming") + ``` + +#### string sourceMediaRep(string sourceNode) + +Returns the name of the media representation currently selected by the Switch Group corresponding to the given `RVFileSource` node. + +When `sourceNode` is an empty string "", then `sourceMediaRep()` returns the name of the currently selected media representation corresponding to the first Switch node found at the current frame. + + +#### string[] sourceMediaReps(string sourceNode) + +Returns the names of the media representations available for the Switch Group corresponding to the given RVFileSource node. + +If `sourceNode` is "", then `sourceMediaReps()` returns all the possible source media representation names. + + + diff --git a/doc/rv-manuals/rv-mu-programming.md b/doc/rv-manuals/rv-mu-programming.md new file mode 100644 index 000000000..aaebd3191 --- /dev/null +++ b/doc/rv-manuals/rv-mu-programming.md @@ -0,0 +1,2249 @@ +# Mu Programming Language + +## 1. Overview ‌ ‌ + +Mu is principally targeted towards computer graphics applications. The original (usable) version appeared at Tweak Films around 2001-2002. Over the years its syntax and runtime has been refactored and evolved from a simple shading language to a nearly full featured general language. However, Mu is still probably best suited for computer graphics than other computing tasks. + +The following discussion is mostly a result of experience in the feature film special effects industry (which I’ll call film). One could argue that computing tasks for film are extreme in many cases and that achievements and trends there tend to trickle down into related CG disciplines (like games and television post production). Indeed, over a short period of time the distinction between these CG disciplines has blurred. + +Mu is an attempt to unify a number of disparate uses of “scripting” and compiled languages in CG. Using Mu in conjunction with C++ applications tends to hit a sweet spot; since Mu is itself written in C++ attention has been paid to making it easy to embed and use in those applications. The static type system has a number of advantages that lead to “better” user code and often easier to maintain. + +Some background on computing in the CG film industry is useful here. To start with, the big computational tasks which affect application language choice fall into these catagories: + +### 1.1 Rendering + +While there are a number of different kinds of rendering software, there are two that are used most in film production: renderers based on the Reyes algorithm (Pixar’s RenderMan being the obvious example), and ray tracing (Mental Images’ Mental Ray possibly producing the most ray-traced pixels on film to date. Pixar’s Photorealistic RenderMan has actually become a ray tracer as well, and Mental Ray is really a hybrid scanline renderer, but for purposes of this discussion I’ll just resort to blatant over-simplification.) These two algorithms have very different profiles: the Reyes algorithm shines on a fast SIMD architecture with fast memory cache lines, and ray tracing (which typically results in a lot of point sampling) benefits mostly from raw speed. Typically render times follow the so-called *one hour rule* (It *used* to be one hour; now its pushing two to three hours.): the rendering scene for a given shot will increased in complexity until the render time hits one hour at which point the complexity will no longer increase. + +Its not atypical for a single frame to require gigabytes of image data (textures) and geometry. This is typically multiplied by the number of frames in a shot — which has unforunately *increased* over the last few years. With the current trend towards stereoscopic film and all CG productions (feature CG animation) this is becoming even more extreme. Tricks like locally caching data, wavelet compression of images, and procedural shaders are often used to reduce the data and throughput complexity. Some newer real world sampling techniques like BDRF, and real time motion and 3D geoemtry capture have futher increased data demands on renderers. + +Its not surprising that production renderers have historically been written in C and more recently C++. These languages allow application programmers to very carefully manage computing resources. The cost of course is complexity of the code and long debugging periods. But usually its worth it: the renderer’s efficiency can directly translate into time and money (and there is a very strong incentive to minimize these!). ‌ ‌ + +Mu has been used for two purposes with regard to rendering: as a shading language (like the RenderMan shading language) and as scene generation and storage language. The first use was for scene generation as a particle instancing language. + +### 1.2 Compositing + +Compositing is similar to rendering: multiple images are rerendered into a new image. In the process pixels may be modified, moved, or generated. While not as compute intensive as 3D rendering, compositing can consume substantial resources as well. + +Recently a trend has developed where renderers produce “partially” rendered images which are actually coefficents in the rendering equation; these are then recombined with new coefficients in the compositing software thereby granting more flexibility after rendering. For example, each light in a scene is effectively turned on by itself creating a layer which is then dialed up and down in the compositor (since light can be linearly combined). This has resulted in a much larger input image set than previously. + +Compositing software is fairly complex compared to most software. Unlike renders, compositing software requires a good deal of user interface to make it usable. + +Like a shading language, a high-level compositing language is typically SIMD. Mu has been used in this context to make a per-pixel compositing scripting language. + +### 1.3 Animation and Modeling + +Animation and modeling software can be separate, but the trend has been towards large swiss army knife feature sets which include *everything* you might want to do in animation and modeling combined. The software used for film (Alias’ Maya for example) is the result of 20 years of evolution. These programs are huge and typically deal with complexities exceeding CAD software (in some uses they are the CAD software). It would not be far fetched to argue that some of these programs are the most complex software written to date. + +Most require very workstation computers, fast graphics cards, fast network I/O and have massive amounts of user interface which is often procedurally generated. They are dump- ing grounds for advanced algorithms spanning computational geometry to computational physicals to signal processing. + +Most of the current crop of 3D modeling and animation packages are “scriptable”. This usually means they have a fairly high-level interpreted language availble for model and animation construction or even as an extension language for adding novel behavior. Usually the language is also used to control and define the user interface of the application (since this is typically a good design choice for large software projects). In addition to the above, Maya even uses the its language (MEL) as its scene file format. + +Mu is ideally suited for these same tasks: its a very high level language with specific built-in types which are typically required for animation and modeling software. It can be used as an interpreted language or it can be compiled. The application can add opaque types and APIs to Mu which easily bind to internal functions. Since Mu was designed for perforance, the language does not become a huge bottleneck even during scene evaluation. + +### 1.4 Simulation ‌ + +Physical simulation has been a part of CG since its inception. Over time this has only increased. Simulation software for film has usually been embedded in *procedural animation* systems which allow the user to programmaticly control physical elements in time and space. + +So to sumarize the computing landscape in CG film: + +* **Large datasets are common.** Many applications will use one minus the number of bytes available to them. +* **Performance is really important.** The artist’s attention span is closely related to ren- dering and simulation times. +* **Workers usually have informal training for programming.** +* **Workers are skilled tool users.** + +## 2. Primitive Types ‌ + + +Mu types have two kinds of semantics: reference and value. Types with value semanitics are always copied when assigned or passed to a function as an argument. Basic number types all have value semantics. + +### 2.1 Boolean type + +Mu has a type for boolean values called `bool` and its two constants true and false. + +Boolean values do not cast to other integral values by default as in the C language. So many C/C++ idioms are not applicable in Mu. + +``` +int a = 0; +if (a) doit(); // error + +``` + +The above example does not work in Mu (Well this isn’t entirely true: you can make a cast operator from int and float to bool and get the C++ behavior). The int will not cast to a bool. The correct way to do the above is: + +``` +int a = 0; +if (a == 0) doit(); // ok + +``` + +All of the conditional constructs take the bool type as their test argument. + +### 2.2 int64, int, short , and byte types. + +Mu has four integral value types. The int64, int, and short types are represented as signed twos complement. The int64 type is 64 bits, the int type is 32 bits and the short type is 16 bits. byte is 8 bit unsigned. The binary representation in memory is machine dependant. + +The integral types all obey basic arithmetic operations as well as the bitwise operators. None of the integral types cast to the boolean type bool automatically. + +There is a distinction between the `byte` and `char` type in Mu. The char type is not assumed to be of any particular size. There are no “unsigned” integral types. + +### 2.3 Floating point types. + +The floating point type is called float and is a 32 bit IEEE floating point number. Mu can also be compiled with half (a 16 bit floating point number) as defined by ILM’s Imath library. The usual arithmatic operators work on floats. Underflow and overflow should behave consistantly across platforms. + +Mu floats do not throw exceptions by default. + +### 2.4 Character type. ‌ + +The `char` type represents a unicode character. + +To make a character constant in Mu, use signle quotes around a single character: + +``` + char c = ’x’; + +``` + +For unicode values when the input file is not encoded as UTF-8 or another accepted coding, you use the unicode escape sequence to specify non-ASCII characters: + +``` + char c = ’\u3080’; \ + +``` + +Characters may be cast to strings. + +``` + string s = ’c’; // string => "c" \ + +``` + +(See section on strings for more info). Operations on the char type are all related to distances between characters: + +``` + +int diff = "c" - "a"; // int => 2 +char a = ’a’; +char b = a + 1; // next character after ’a’ +a++; // next character +assert(a == ’b’); + +``` + +### 2.5 The void Type. + +The `void` type is useful in the context of function declarations. You cannot make a variable of type `void` since it has no value. The `void` type indicates that absence of a return type for a function definition: + +``` + function: print_it (void; int i) { print(i + "\n"); } + +``` + +In the example the function `print_it` has no return value so the type void is used to indicate that. + +### 2.6 The nil Type. + +The value `nil` is used to set a reference variable to refer to “nothing”. It is analogous to NULL in C/C++, null in Java, nil in lisp, or None in Python. + +`nil` is of type `nil` . The type is special in that it can be automatically cast to any reference type. + +You cannot test any type value against nil using ==. Instead operator eq or neq must be used: + +``` + + string s = ‘‘hello’’; \ + if (s eq nil) print("error\n"); // syntax error + +``` + +### 2.7 Vector Types + +The vector types are primitive types which are passed by value. To specify a vector type you use the vector type modifier: + +``` +vector float[4] hpoint = {1, 2, 3, 4}; +vector float[3] point = {1, 2, 3}; +vector float[2] ndc = {0.0, 1.0}; + +``` + +There are currently only three types which are allowed to be used with the vector type modifier. These types are: float[2], float[3], and float[4]. + +Vector types allow access to their members using member variables. The member vari- ables are called x, y, z, and w. In addition you can use the indexing notation (operator[]). For the two dimensional type, z and w are not available. For the three dimensional type, w is not available. For example, here is a normalize function: + +``` +function: normalize (vector float[3]; vector float[3] v) +{ +float len = math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); +return v / len; +} + +``` + +You can also set the members directly: + +``` +vector float[3] v = {1, 2, 3}; +v.x = v.y; + +``` + +To make arrays of vectors, it may be necessary to include parentheses in the type definition: + +``` +(vector float[3])[] array_of_points; + +``` + +See also symbolic assignment operator for creating type aliases. + +### 2.8 Function Types + +Function types are declared much like functions themselves. The type of a function encodes its return type and all its argument types (whether or not it has default values). + +For example, this is the type for a function that takes two ints as arguments and returns an int: + +``` +(int;int,int) + +``` + +Whitespace inside the declaration is ok, but for clairity it is omitted here. In practical usage, you might want to create a variable and assign a function to it like so: ‌ + +``` +function: add (int; int a, int b) { a + b; } +(int;int,int) x = add; + +``` + +Since functions are first class objects in Mu, you can make arrays of them: + +``` + (int;int,int)[] int_funcs = { add }; + +``` + +For example, this makes a dynamic array of functions which return an int and take two ints and initializes it to have a single element: the function add. + +Of all the type declaration syntax, the function type syntax is the most complex. Here’s an example of a function type which takes a dynamic array of string and returns a function that returns an int and takes a float[4,4] matrix as an argument: + +``` + ((int;float[4,4]);string[]) \ + +``` + +See the section on symbolic assignment for how to clean up a mess like the above. + +#### 2.8.1 The Ambiguous Function Type + +One problem that arises in a type system with function overloading is ambiguity resolution (if ambiguity is allowed). When a function type expression is evaluated for an overloaded function, the result is the ambiguous function type: + +``` +mu> +(;) => print \ + +``` + +In this case, the print function is overloaded and therefor is returned as type (;). An expression or variable of type (;) cannot be evaluated using operator(). However, the type will automatically cast to a callable function type (as seen in this interpreter session): + +``` +mu> (void;float)(print) +(void;float) => print (void; float) + +``` + +In this case the result is no longer ambiguous. If you attempt to cast to a function type for which there is no overloaded instance, the cast will throw an exception. + +## 3. Built-in Types ‌ ‌ ‌ + + +Mu has built-in syntax for various types of arrays, lists, maps, and strings. + +### 3.1 Lists + +Lists in Mu can be formed directly by enclosing a comma separated list of values in brackets: + +``` + [1, 2, 3, 4, 5] + +``` + +Mu will infer the type of the list. The type of a list is expressed by enclosing a type name inside brackets. So the type of the above expression is [int] (a list of int). Mu does not have heterogeneous lists (without defining them yourself). So you can’t do something list this: + +``` + [1, "two", 3.14] // syntax error + +``` + +There are three operators on lists and some syntactic sugar. The operators are cons, head, and tail. + +#### 3.1.1 cons + +`cons ( **[** ’a **]** ; ’a head, **[** ’a **]** list )` [Function] + +Returns a new list with *head* as the first element followed by each of the elements in *list* . + +The cons operation can be made by either using the function cons or using the operator :. The first argument (or left operand) is any value. The second argument (or right operand) must be a list of the first argument type. So for a type ’a as the first argument, the second argument must be `[’a]` . For example the following are equivalent and result in the list `[1, 2, 3, 4]` . + +``` +1 : [2, 3, 4] +cos(1, [2, 3, 4]) + +``` + +#### 3.1.2 head and tail + +`head ( ’a; **[** ’a **]** list )` [Function] + +Returns the first element in *list* . + +`tail ( [ *’a* ] *;* [ *’a* ] list )` [Function] + +Returns the list of all elements in *list except* the first element in the same order they appear in the *list* . + +The head and tail functions return the first element in a list (head) or the rest of the list (tail). tail will always return a list or nil if there is no tail. + +``` +head([1, 2, 3]) ⇒ 1‌ +tail([1, 2, 3]) ⇒ [2, 3] +tail([1]) ⇒ nil + +``` + +#### 3.1.3 Pattern Matching with Lists + +The list syntax can also be used to pull apart the values in a list by using it in a pattern: + +``` +let [x, y] = [1, 2]; +assert(x == 1); +assert(y == 2); + +``` + +Note that the number of elements in the list must match the number of elements in the pattern. If they do not match an exception will be thrown at runtime (or at compile time if can be detected). + +The cons operator : can also be used in patterns. For example: + +``` + let h : t = [1, 2, 3] + +``` + +The value of h will be 1, the value of t will be the list [2, 3]. This is equivalent to doing the following: + +``` + let x = [1, 2, 3], + h = head(x), + t = tail(x); + +``` + +The cons pattern will throw if the list on the right-hand-side has the value nil. + +### 3.2 Tuples + +Tuples are collections of a fixed number of heterogeneous types. Another way to think of them is as anonymous structures. + +Tuples have a special syntax used for construction and destruction (pattern matching) which make them easy to use. To construct a tuple of two or more values, simple enclose them in parenthesis like so: + +``` + (1, "the number one") + +``` + +Similarily, the type of the above expression is (int,string). So tuple type declarations look similar to tuple values: + +``` + (int,string) x = (1, "the number one"); + +``` + +Tuple values (and tuple types) can be nested as well: + +``` + ((int,string),float) x = ((1, "the number one"), 3.141529); + +``` + +There is one exceptional circumstance with tuple values: if one of the elements is nil, the tuple type will be ill-defined and will produce a syntax error: ‌ ‌ + +``` + (1, nil) // error + +``` + +There is currently no way to make a *singleton* tuple value (a tuple of one element). + +#### 3.2.1 Indexing a Particular Element in a Tuple + +Each element in a tuple is given a numerical name. The first element is called _0 followed by _1 followed by _2, and so on. For example to get the second element: + +``` +(1, "two")._0 ⇒ 1 +(1, "two")._1 ⇒ "two" + +``` + +The value of the first expression is 1 because the 0th element is begin indexed. It is not possible to dynamically index tuple elements because each element may have a unique type. The best way to think about a tuple is a struct with elements named _0 through _N that you don’t need to declare. + +#### 3.2.2 Pattern Matching with Tuples + +Usually you don’t bother with the type annotation and use pattern matching instead: + +``` + let x = (1, "the number one"); + +``` + +`x` in this example has type (int,string). The tuple syntax can also be used when pattern matching to pull values out of the tuple (destruction): + +``` + let (a, b) = (1, "the number one"); \ + +``` + +So in this case a will have the value 1 and be of type int and b will have the value "the number one" and be of type string. Tuple patterns may also be nested: + +``` + let (a, (b, c)) = (1.234, (2, "three")); \ + +``` + +The tuple pattern will throw if the matching tuple value is nil. + +### 3.3 Dynamic Arrays + +Mu has two different kinds of arrays: dynamic and fixed. Unlike C, the type specifier completely encodes the array type. For example: + +``` +float[] foo; +float foo[]; // syntax error + +``` + +The first line makes a dynamic array of floats. The second line produces a syntax error in Mu because the C array syntax is not permitted. The dynamic array object has a number of member functions on it which can be called: + +``` +foo.size(); // returns number of elements in foo foo.push_back(1.0); // appends the number 1.0 to the end of the array foo.front(); // returns a reference to the first element in foo foo.back(); // returns a reference to the last element in foo.clear(); // sets the size to 0 +foo.resize(10); // resizes foo to 10 elements. +foo.rest(); // returns all but the first element as an array foo[0]; // returns the 0th element + +``` + +The array type declarations can be nested: + +``` + float[][] foo; // array of array of floats foo.push_back( float[]() ); // add a float[] to foo foo[0].push_back(1.0); // add a float to the 0th element of foo + +``` + +Note that this is not the same as making a multidimensional array. You cannot currently make a multidimensional dynamic array. However you can make a multidimensional fixed array. + +### 3.4 Fixed Size Arrays + +Fixed arrays are similar to dynamic arrays, but the size is encoded in the type: + +``` + float[10] foo; // make a 10 element fixed array of floats + +``` + +The fixed array types have fewer operations than the dynamic array type (since they are a more restrictive type). However the basic array syntax is shared: + +``` +foo.size(); // returns size (ok) \ +foo.push_back(1.0); // syntax error! (no push_back function) +float x = foo[5]; // 5th element +foo[5] = x; + +``` + +Like dynamic arrays, you can make arrays of arrays and mix them with dynamic arrays: + +``` +float[10][4] foo; // 4 arrays of 10 floats +float[10][] foo; // a dynamic array of 10 float fixed arrays + +``` + +In addition, fixed arrays may have multiple dimensions. This is done by adding comma separated lists of sizes: + +``` +float[4,4] foo; // a 4x4 matrix of floats \‌‌ +float x = foo[1,1]; // set x to 1st row 1st column value foo[3,1] = x; // copy x to the 3rd row 1st column value +float[4,4,4] foo; // a rank-3 tensor + +``` + +For graphics applications, its common to + +### 3.5 Maps + +### 3.6 Sets + +### 3.7 String Type. + +Strings conceptually contain a sequence of char type. However, the string type is currently independant of any sequence type. Strings in Mu are immutable; you cannot change the value of a string. Instead you make new strings by either concatenating existing strings or by using the formatting operator (%). + +`strings` can be constructed as a constant from a sequence of characters in double or triple quotes. (See String Constants for more details). + +``` +string s = "Hello World"; +string y = """A long string with +possible newlines embedded "quotes" and other +difficult characters"""; + +``` + +There are a few basic operations on strings: + +hash size split substr + +In addition, any value can be cast to a string (or rather a string can be constructed from any value). + +#### 3.7.1 Formatting + +Strings can be formatted using the % operator. The left-hand-side of the operator is a format template string and the right side is a single value or a tuple of values that are substituted into the template much like C’s printf function. + +For example: + +``` +"%0.2f" % math.pi ⇒ "3.14" +"%d" % 123 ⇒ "123" +"%s and %d" % ("foo", 123) ⇒ "foo and 123" + +``` + +#### 3.7.1.1 Formatting Directives + +### 3.8 Regular Expressions and Syntax + +### 3.9 Types Defined by Applications or Native Modules ‌ + +Some applications or native modules may provide an opaque type. These are types that appear to have no structure from Mu, but have operators and functions which can operate on them. There is no way to define an opaque type in Mu, but a similar effect can be had by defining an enumeration as a union. + +An example of an opaque type can be found in the system module. This module contains analogues to POSIX functions and types in Mu. One of the declared types FILE represents a standard C library file descriptor. This type cannot be deconstructed but can be operated on by fopen, fclose, fread, and fwrite among others. Opaque types often represent external resources like FILE. + +## 4. User Defined Types: Structs, Classes, and Unions ‌ + + +### 4.1 Records + +### 4.2 Object Oriented Features: Classes + +### 4.3 Tagged Union Type + +Mu has a type-safe data structure called a *union* which makes it possible to deal with values from more than one type. The implementation of a union in Mu is different than languages like C and C++ which give unrestricted access to values. + +Unions declarations have the following syntax: + +``` + union: union-name { cnstr1 [ type1 ] [ | cnstr2 [ type2 ] ]* } + +``` + +Here’s a specific declaration: + +``` + union: Foo { A int | B float | C string } + +``` + +The example declares a union called Foo which has three constructors: A, B, and C. Each constructor is separated by the | character. You could read the declaration as “Foo is an int which we’ll call A *or* a float which we’ll call B *or* a string which we’ll call C”. So, the constructor A when called as a function takes an int, B takes a float, and C takes a string. + +From a theoretical perspective, a union can be thought of as a type which includes the values of other types plus a *tag* for each value. In order to retrieve the value of a union, the tag must be supplied which matches the tag on the value. In other words, to get a value from a union you need to know what its tag is. This type of union is called a *tagged union* as opposed to the C-like *untagged union* where only the value is stored. + +In Mu, the union includes multiple *constuctors* (which are similar to class constructors) which are used as tags. Each constructor in the union must have a unique name. The constructor has a type associated with it; values from that type are stored in the union with the constructor tag. To make a union object you must call one of its constructors. + +Continuing the above example of Foo, we can use the constructors to make instances of Foo like this: + +``` +Foo x = Foo.A(123); +Foo y = Foo.B(3.141529); +Foo z = Foo.C("hello"); + +``` + +Or to make it easier to get at the constructors use the type: + +``` +use Foo; +Foo x = A(123); +Foo y = B(3.141529); +Foo z = C("hello"); + +``` + +In any case, x, y, and z are all of type Foo. The arguments and overloading of the con- structors *are the same as the underlying type’s* . So, for example the string has constructors like these: ‌ + +``` +string(1) ⇒ "1" +string(1.234) ⇒ "1.234" + +``` + +which means the Foo.C() can be called similarily because its underlying type is string: + +``` +Foo x = C(1); ⇒ x = Foo.C("1") +Foo y = C(1.234); ⇒ y = Foo.C("1.234") + +``` + +So why not allow casting to unions? For example this seems like it would be ok: + +``` +Foo x = 1.123; error Can’t cast to a union value + +``` + +There are two reasons. The first is that constructor arguments for multiple constructors unions may be identical; in that case there is no obvious way to choose the proper one. The second is actually a feature of unions: you can declare multiple constructors that take the same type. So this is ok: + +``` +union Bar { A int | B int | C int } \ +use Bar; +Bar x = A(1); + +let C q = x; error *x’s value was not constructed with C!* +let A w = x; ⇒ *ok! w = 1* + +``` + +Bar has three constructors and the all take int. So the union values are really the values of the int type alone. However, the union constructor (tag) is still required to get the value. You could think of the union in this case making *flavors* of int. + +#### 4.3.1 Retrieving union values. + +To retrieve a union value, you must use some form of pattern matching. There are no fields of a union as there are in the C languages. For example, to get the int out of the Foo union declared above: + +``` +Foo x = Foo.A(123); +int Foo.A i = x; ⇒ i = 123 + +``` + +If the pattern does not match (which means the value in the union could not have been created with the pattern constructor) an exception will be thrown: + +``` +Foo x = Foo.A(123); \‌ +let Foo.B f = x; error throws: x will only match constructor A + +``` + +This form of pattern matching used for union *deconstruction* is only used in cases where the type of the union’s value is known ahead of time (so that the pattern will not fail). When type is not known, the case statement or expression is used instead. The case pattern syntax allows the use of union constructors like let. + +``` +Foo x = Foo.A(123); \ +case (x) + +{ + A i -> { print("x’s value is the int %d\n" % i); } + B f -> { print("x’s value is the float %f\n" % f); } + C s -> { print("x’s value is the string %s\n" % s); } +} + +x’s value is the int 123 + +``` + +Note that we didn’t have a use statement in the last example: in a case statement where the value being matched against is a union, the union’s type is automatically being *used* inside of it. So we don’t have to prefix the constructor pattern with the union name. + +#### Enumerations as Union Constructors + +There is a special case of a union constructor which has the void type. To declare such a constructor simply omit its type. While the union may have no retrievable value of that constructor, it will still be *tagged* as such. Using this mechansim enumerated types can be created where the constructor **is** the value. + +``` + union: Weekday { Monday | Tuesday | Wednesday | Thrusday | Friday } \ + +``` + +These constructors take no arguments, and there is an exception to the usual syntax for functions in the case of these constructors: no parenthesis are needed to call them. + +``` + Weekday yesterday = Weekday.Monday; \ + +use Weekday; +Weekday today = Tuesday; + +``` + +You cannot use let to match against enumerated unions. Only the case statement (or expression) can be used: + +``` +case (today) \ +{ + Monday -> { ... } + Tuesday -> { ... } + Wednesday -> { ... } + Thursday -> { ... } + Friday -> { ... } +} + +``` + +## 5. Functions and the Function Type. ‌ + + +Functions play a ubiquitous role in Mu. Constructors, operators, iteration constructs, and more are implemented as functions which can be overriden, passed as objects, or modified. + +### 5.1 Function Declaration + +Mu functions are declared using either the function: keyword (aka \:) or the operator: keyword. When binding a function to a symbol, the form is: + +``` + function: identifier signature body + +``` + +The *identifier* can be any legal identifier (with some restrictions). The *signature* portion of the declaration looks like this: + +``` + ( return-type ; type0 arg0, type1 arg1, type2 arg2, ... ) + +``` + +This object, a signature, indicates the return type and all of the arguments to the function. The first part of the signature, the *return-type* must always be present. The second part, the argument list, can be empty indicating that there are no arguments to the function. The last portion the *body* is a code block. Here’s an example function that computes factorial: + +``` +function: factorial(int; int x) +{ +return x == if 1 then 1 else x * factorial(x-1); +} + +``` + +### 5.2 Operator Declaration + +Mu allows operator declaration and overloading. In Mu operators are just functions with syntactic sugar. The operator precedence cannot be changed, but any operator can be overloaded. The operator declaration syntax is similar to the function declaration syntax: + +``` + operator: operator-token signature body + +``` + +The *operator-token* is a special sequence of characters that describes the operator. In most cases this is the same as the operator in use. For example, the operator ^ is not defined for floating point numbers. But you can make it into a power operator: + +``` +operator: ^ (float; float base, int power) +{ + float answer = base; + + for (int i=0; i < power - 1; i++) + { + answer *= base; + } + + return answer; +} + +``` + +Some operators are overloaded. In order to distinguish between the overloaded versions, the *operator-token* is different the literal operator token. For example the postfix increment + +operator var++ is different than the prefix increment operator ++var. Each has a different special token as show below. + +This is the current list of special operator tokens: + +``` +_++ postfix increment +_-- postfix decrement +++_ prefix increment +--_ prefix decrement +?: conditional expression (takes three arguments) + +``` + +### 5.3 Function Overloading + +Functions may be overloaded. In other words, multiple declarations of the same function may be made as long as their arguments differ in type or number. For example: + +``` +function: area (float; triangle t) { ... } +function: area (float; circle t) { ... } +function: area (float; rectangle t) { ... } +function: area (float; shape t) { ... } + +``` + +When the area function is applied to a circle, Mu will choose the appropriate version of the area function. In the above example, assuming that triangle, circle, and rectangle are all derived from the type shape, you can apply the area function to any other object that is derived from shape as well (Mu will choose the last area function above). When a direct match for an overloaded function is not found, Mu will attempt to use casting rules to make the function call. + +### 5.4 Default values + +Functions may have default values as long as every parameter after a parameter with a default value also has a default value. (This is the same rule that C++ has concerning default parameter values.) + +``` +function: root (float; float a, float b = 2) +{ +return math.pow(a, 1.0 / b); +} + +``` + +The function "root" can be called either like this: + +``` + root(2.0, 2.0); + +``` + +or this: + +``` + root(2.0); + +``` + +either way will return the value of math.pow(2, 0.5). It is an error to declare a function like hits: + +``` +function: root (float; float a = 2, float b) +{ +// ... +} + +``` + +In this case parameter "b" follows parameter "a" which has a default value. Because "a" was declared with a default value, "b" must also be declared with a default value. + +### 5.5 Returning from a Function. + +There are two ways to return a value from a function. The first way is identical to C/C++: use the return statement: + +``` + return return-expression + +``` + +The *return-expression* must be the same as the function return type or something that can be cast to it automatically. The return statement may appear anywhere inside the function. There can be multiple returns from a function. + +Alternately, since blocks of code are also expressions, you can omit the return statement. + +``` +function: add (int; int a, int b) +{ + a + b; +} + +``` + +This is not a syntax error because the last statement of the code block returns an int and therefor becomes the return value of the function. This is unlike C/C++ which require the return statement exist unless the function returns void. + +### 5.6 Unnamed (Anonymous) Functions + +An anonymous function can be created by omitting the function name in a function defi- nition. For example, the good old add function as an anonymous function looks like this when fed to the interpreter: + +``` +mu> function: (int; int a, int b) { a + b; } +(int;int,int) ⇒ lambda (int; int a, int b) (+ lambda.a lambda.b) + +``` + +The result is a function object. You can assign this value to a variable if you like and call the function through it. + +``` +mu> global let x = function: (int; int a, int b) { a + b } +(int;int,int)& ⇒ lambda (int; int a, int b) (+ lambda.a lambda.b) +mu> x(1,2) + int ⇒ 3 + +``` + +Unambiguous function objects all have operator() defined for them. So you can call the function either through a variable or an expression that returns a function. + +## 6. Constants and Initialization ‌ ‌ + + +### 6.1 Integral Type Constants + +hex octal decimal int64 v int + +### 6.2 Floating Point Constants + +engineering notation + +### 6.3 String Constant Syntax + +single Quotes. unicode escapes. + +### 6.4 String Constant Syntax + +Double quotes. Unicode escapes. Control escapes. Triple quotes. String constant juxtapo- sition. Handling of newlines in strings. + +### 6.5 USer and Built-in Type Constants + +The reference types which have constructors can be initialized using curly brackets when assigned to a type annotated variable. This applies to all types not just collections. However, this syntax is most often used with collections. + +Using arrays as an example: + +``` + float[4] foo = {1.0, 2.0, 3.0, 4.0}; + +``` + +Multidimensional arrays: + +``` + float[2,2] foo = {1.0, 2.0, 3.0, 4.0}; + +``` + +also have the form of single dimensional arrays. However arrays of arrays: + +``` + float[2][2] foo = { {1.0, 2.0}, {3.0, 4.0} }; + +``` + +use nested brackets. Similarily dynamic arrays can be initialized: + +``` + float[] foo = {1, 2, 4, 5, 6, 7}; + +``` + +trailing commas are accepted. You may also use non-constant values in an array con- struction: + +``` +float a = 5.0; +float[] foo = {a, a*2, a*4}; + +``` + +#### 6.5.1 Constants + +In addition, you can make a constant by putting the type in front of the braces. For example if there is a function bar() that takes a dynamic array of ints, you can supply a constant to it like this: + +``` + bar( int[] {1, 2, 3, 4} ); + +``` + +#### 6.5.2 Use with Patterns ‌ + +The initializer syntax will not work with patterns because the type of the expression is underconstrained: + +``` + let a = {1, 2, 3 }; // error + +``` + +The left-hand-side of assignment is a pattern, and the right hand side has an unknown type. In this case it could be a list, a dynamic array, a fixed array or a struct or class. So the type of a is ill-defined. If in this case we meant a to be of type int[] for example, we could use the constant syntax and do the following: + +``` + let a = int[] {1, 2, 3}; // ok + +``` + +This is well defined. + +### 6.6 Converting Basic Constants + +Mu recognizes some basic constant suffixes. More can be added. Suffixes are defined in the suffix module as normal functions. For example the function suffix.f is declared like this: + +``` +module: suffix +{ + function: f (float; int i) { i; } function: f (float; int64 i) { i; } +} + +``` + +In use, the suffix would appear *after* a integral numeric constant such as 123f. The f suffix function is called by the parser to make a constant float out of the token 123. Of course you could also just write it 123.0 which would have the same affect. + +A more useful example would be a regular expression suffix: + +``` +module: suffix +{ + function: r (regex; string s) { s; } +} + +``` + +The function **r** could be used to create a list of regular expressions like so: + +``` + ["foo.*"r, "bar.*"r, "[0-9]+"r] + +``` + +As opposed to this: + +``` + [regex] {"foo.*", "bar.*", "[0-9]+" } + +``` + +Or as a way to diambiguate overloaded functions: + +``` +\: foo (void; regex a) { ... } +\: foo (void; string a) { ... } + +foo("[0-9]+"r); // calls first version +foo("[0-9]+"); // calls second version + +``` + +For regular expressions, this has the added benefit of forcing the compilation of the regular expression during parsing instead of at runtime. + +A more (possibly sinister) usage of suffixes is to represent mathematical or physical constants (or units) using them: + +``` +module: suffix +{ + function: pi (float; float x) { x * math.constants.pi; } + function: pi (float; int x) { x * math.constants.pi; } + function: i (complex float; float x) { complex float(0,x); } +} + +let c = 6 + 12i; // alternate complex number constant form! +let twoPI = 2 pi; // questionable use of suffix! + +``` + +### 6.7 Anonymous Function Constants + +An anonymous function can be created by omitting the function name in a function defini- tion. + +For example, the good old add function as an anonymous function looks like this when fed to the interpreter: + +``` +mu> function: (int; int a, int b) { a + b; } +(int;int,int) => lambda (int; int a, int b) (+ lambda.a lambda.b) + +``` + +The result is a function object. You can assign this value to a variable if you like and call the function through it. + +``` +mu> global let x = function: (int; int a, int b) { a + b } +(int;int,int)& => lambda (int; int a, int b) (+ lambda.a lambda.b) +mu> x(1,2) +int => 3 + +``` + +Unambiguous function objects all have operator() defined for them. So you can call the function either through a variable or an expression that returns a function. + +## 7. Polymorphic and Parameterized Types ‌ + + +### 7.1 Type Variables + +### 7.2 Typeclasses: Families of Types. + +## 8. Variables ‌ + + +Mu types have two kinds of semantics: reference and value. Types with value semanitics are always copied when assigned or passed to a function as an argument. Basic number types all have value semantics. + +### 8.1 Reference Type Construction Semantics + +Reference types are allocated by the programmer or the runtime environment. For the programmer, this is done by calling one of the constructors for the type. For example, you can create a string object like this: + +A variable that holds a reference types is actually better defined as a variant type; the value can be a member of the storage type or it can be nil. + +``` +string foo = string(10); +string bar = string(); + +``` + +which creates the string "10" from the integer 10 for the variable foo and the default empty string for the variable bar. When a variable is declared without an initializer, Mu will supply the default initializer or nil if there is none: + +``` +string baz; // these are the same +string baz = string(); // + +``` + +If you explicitly want to set an object type variable to nil, you should do that when intializing it: + +``` +string foo = nil; // no object is constructed + +``` + +This is not true of arrays however: + +``` +string[] bar; // creates string[] object +bar.resize(1); +bar[0].size(); // whoops! that element is nil! +bar[0] = string(); // no longer nil + +``` + +Because objects are passed by reference, setting one variable to another can have inter- esting results: + +``` +string foo = "ABC"; +string bar = foo; +bar += "DEF"; +print( foo + "\n" ); + +``` + +The output of the above example is "ABCDEF". Because the variables foo and bar are references to the same underlying string object, mutating operations through the bar variable will be visible through the foo variable. You can test for this condition using the operator eq: + +``` + if (foo eq bar) { print("they’re the same!\n"); } + +``` + +If you want to prevent this behavior, you need to make a copy: + +``` +string foo = "ABC"; +string bar = string(foo); +bar += "DEF"; +print( foo + "\n" ); + +``` + +In this case the string "ABC" will be printed because foo is referencing a different object than bar. + +### 8.2 Kinds of Variables. + +There are three kinds of “variables” in a Mu: global, local, and fields. + +Global variables can be declared in any scope by preceding the variable declaration with the global keyword. There is a single location in the Mu process which represents a global variable and its lifetime is the same as the process. + +The scope of the global variable’s symbol declaration determines how and where the global variable can be accessed just like any other symbol. For example to make a global variable that is also globally accessable requires declaring in the top-most scope or a scope accessable from the top-most scope. For example all of the following are global variables that are accessable from any part of a Mu program: + +``` +global int x; +module: amodule { global int y; } + +function: foo (void;) { print(x); } // ok +function: bar (void;) { print(amodule.y); } // ok + +``` + +However, if the variable is declared in a function scope, then it is only accessable by parts of the code which can access symbols at the same scope: + +``` +function: foo (void;) { global int x; } +print(x); // error! can’t access it + +function: bar (void;) +{ + global int x; + function: foo (void;) { + print(x); } // ok print(x); // ok +} + +``` + +Local variables exist on the program stack and exist only while the declaration scope is active. In the case of a function, this is while the function is executing. The variable has a unique copy for each function invocation just like a function’s parameters. + +Local variables are accessable on in the scope in which they are declared: + +``` +int x; +print(x); // ok +function: foo (void;) { print(x); } // error function: bar (void;) +{ + int x; + print(x); // ok +} + +``` + +Fields are variables that are part of a larger object. For example, the two dimensional vector type has three field variables called “x” and “y”. These variables are addressable using the scope (dot) notation: + +``` +vector float[2] v; +v.x = 1; +v.y = 2; + +``` + +The lifetime of a field is the same as the lifetime of the object (its scope). + +### 8.3 Variable Declarations + +Mu is a statically typed language. It currently does very primitive inferencing and therefor requires type annotation for variables in many cases. Variable symbols are assigned a type which is immutable – this is called its storage type. The variable will only ever hold values of its storage type. + +The annotated variable declaration statement has the one of following forms: + +``` + type variable [, variable ...] +type variable = expression [, variable = expression, ...] + +``` + +In usage this looks like this: + +``` +int x; int y = 1; +int z = 1, q = 10; + +``` + +## 9. Pattern Matching ‌ + + +Besides the usual imperative variable assignment syntax, the let statement can be used to match patterns. There are two benefits to using let with pattern matching: multiple nested assignments can be made simultaneously and the types of the variables can be figured out by the parser so you don’t have to annotate them. + +Similarily, the case statement and expression can match patterns and values while as- signing symbols to values without type annotation. + +The ability of the parser to figure out types of symbols with annotation is called type inference. This is a common feature of functional languages in the ML family (O’CaML and Haslkell for example). Mu currently uses a very restricted type inference algorithm. + +### 9.1 Assigning Variables with Patterns + +The let statement defines symbols in the current and nested scopes. Symbols assigned using + +let are immutable (the values cannot be changed). The general form of the statement is: + +``` +let let-pattern1 = expression1 \ +[, let-pattern2 = expression2, ...] ; + +``` + +The *let-pattern* can be any of the following: + +* A single symbol which is assigned the value of its expression. This is the simplest usage of let and will never result in an exception being thrown. For example let x = 0 assigns the type int to the symbol x and causes the value 0 to be bound to it. +* A tuple destructor pattern which binds the pattern symbols to each of the elements of a tuple expression. You can only use the tuple pattern if the bound expression is a tuple type. In addition the number of elements in the pattern must match the number of elements in the expression tuple type. + +``` + let (a, b, c) = (1, 2, 3); + ⇒ a = 1, b = 2, c = 3 + +``` + +* A list destructor pattern which bind the pattern symbols to each of the elements of a list expression. Like the tuple pattern, the list pattern requires that the number of symbols in the pattern be equal to the number of elements in the list value at runtime. If the number of elements does not match at runtime or the parser can figure out that the number does not match a parse time, an exception will be thrown. + +``` +let x = [1, 2, 3]; // without list pattern \ + ⇒ x = [1, 2, 3] +let [a, b, c] = [1, 2, 3]; + ⇒ a = 1, b = 2, c = 3 +let [d, e] = [6, 7, 8]; + error num symbols != num elements + +``` + +* A cons pattern which pulls apart the head and tail of a list. This pattern, like the list destructor pattern, requires a list expression. Two symbols are supplied: the head and tail symbol: + +``` +let h : t = [1, 2, 3, 4]; \ + ⇒ h = 1, t = [2, 3, 4] +let h0 : h1 : t = [1, 2, 3]; + ⇒ h0 = 1, h1 = 2, t = [3] + +``` + +* The structure destructor pattern which pulls apart structure fields. This pattern can also be used on tuples and lists, but cannot be used on arrays. + +``` +struct: Foo { int a; float b; } \ +let {a, b} = Foo(1, 2.0); + ⇒ a = 1, b = 2.0 + +``` + +A union type constructor pattern. The expression supplied must be of the union type. If the constructor does not match the value constructor type, an exception will be thrown: + +``` + union: Bar { A int | B float } let Bar.A {x} = Bar.A(1); + ⇒ x = 1 + +let Bar.B {x} = Bar.A(1) + error Expression is of type Bar.A + +``` + +* Finally, the symbol _ (underscore) can be bound to any expression. The expression is evaluated at runtime (if it is not pure), but the value is ignored. + +Each of these patterns can be combined with the others and nested to arbitrary depths. This makes it easy to pull values from inside nested structs, tuples, and lists easily. + +``` +struct: Foo { int a; [int] b; } \ +let ([a, b, c], d, {e, f : g}) = ([1,2,3], 4, Foo(5,[6,7,8])); + ⇒ a = 1, b = 2, c = 3, d = 4, e = 5, f = 6, g = [7, 8] + +let (a, b, _) = (1, 2, [3,4,5,6]); + ⇒ a = 1, b = 2 + +``` + +### 9.2 Pattern Matching to Control Flow + +### 9.3 Case an an Expression + +## 10. Flow Control ‌ + + +Mu has the usual cadre of flow control statements for a C-family language plus a few additions. + +All of the flow control statements are actually functions and they all return a value which depends on the statements they execute. Usually the return value is only useful in the context of a function definition where the last statement in the function is one of the flow control constructs. + +### 10.1 Conditional Execution, the if Statement + +The `if` statement has two forms: one with an else clause and one without. The test expression for an `if` statement must be of type bool. The general forms are: + +``` +if ( bool-expression ) if-true-statement +if ( bool-expression ) if-true-statement else if-false-statement + +``` + +The return value of the if construct function is the return value of the *if-true-statement* or the *if-false-statement* . When there is an *if-false-statement* present, it must return the same type as the *if-true-statement* . + +Because the integral and float types in Mu do not cast to type bool by default, many C/C++ idioms involving the if statement do not work: + +``` +int x = 1; +if (x) doit(); // error, x is not bool +if (x == 1) doit(); // ok + +``` + +Some more examples: + +``` +if (x == 1) \ +{ + doit(); +} +else +{ + stopit(); +} + +``` + +Also note that the C++ ability to declare variables in the test expression are not valid in Mu: + +``` + if (bool foo = testfunc()) doit(); // error + +``` + +Since the test expression must be of type bool, the C++ism would not be very useful in Mu. + +10.6 `case` Statements. ‌ ‌ + +The general form is: + +``` +case ( value-expression ) +{ + case-pattern0 -> { statement-block0 } + case-pattern1 -> { statement-block1 } + case-pattern2 -> { statement-block2 } + ... +} + +``` + +Unlike the `if` statement, the case statement can match over any value type that can appear in a pattern. + +The first matching pattern will cause the corresponding statement to be executed. A simple example matching values might be: + +``` +string s = ...; case (s) + +{ + "one" -> { print(1); } + "two" -> { print(2); } + "three" -> { print(3); } + x -> { print("Don’t know what %s is\n" % x); } +} + +``` + +### 10.3 Conditional Expressions; A Variation on if + +The condition expressions in Mu looks similar to the conditional statements. + +#### 10.3.1 if-then-else Expression + +In the case of the `if` conditional expression the keywords then and else are required. No parenthesis or brackets are required: + +``` + if test-expression then if-true-expression else if-false-expression + +``` + +The *if-true-expression* is only evaluated if *test-expression* evualuates to true. Otherwise, the *if-false-expression* is evaluated. In either case, the value and type of the conditional expression is the same as the evaluated expression. + +The type of the *if-true-expression* and the *if-false-expression* must be the same or case to the type. If the types differ, the parser will attempt to find the least lossy type cast that will make them match. + +The conditional expression can be used anywhere any other expression can be used, but because of its precedence the entire expression may require parenthesis’: + +``` +let x = if somecondition() then 0.0 else 3.1415; \‌ +print(if x == 3.1415 then ‘‘its pi’’ else ‘‘its not pi’’); +let y = x + (if x > 0.0 then 1.0 else -1.0); + +``` + +#### 10.3.2 case Expression + +``` + case test-expression of pattern1 -> expression1 + pattern2 -> expression2 + pattern3 -> expression3 + ... + +``` + +### 10.4 Fixed Number of Iterations Looping. + +The repeat statement has the general form: + +``` + repeat ( int-expression ) statement + +``` + +The *int-expression* is evaluated once. *Statement* is then evaluated that many times. + +``` +// outputs "hello hello hello " \ +repeat (3) print("hello "); + +``` + +### 10.5 Simple Looping + +The `while` and `do-while` constructs have the form: + +``` +while ( test-expression ) statement +do statement while ( test-expression ) ; + +``` + +The *test-expression* must be of type bool. It is evaluted either before *statement* (or after in the case with do-while). If *test-expression* is true, then *statement* will be evaluated. + +In the cast of do-while, the *statement* is always evaluated once since the test expression is evaluated after. + +### 10.6 Generalized for Loop + +The for construct in Mu is similar to the C/C++ statement of the same name: + +``` + for ( declaration-expr; test-expr; tail-expr ) statement + +``` + +The *declaration-expr* , *test-expr* , and *tail-expr* are all optional. If the *test-expr* does not exist, it is as if the *test-expr* where true (the loop never terminates). + +*Test-expr* must be of type bool. + +As in C++, the *declaration-expr* can declare one or more variables: + +``` +// outputs: "0 1 2 " +for (int i = 0; i < 3; i++) print(i + " "); + +``` + +In this case the variable i is in the scope of statement and does not appear outside the loop: + +``` +// outputs: "0 1 2 " +for (int i = 0; i < 3; i++) print(i + " "); + +print(i + "\n"); // error "i" not defined here. + +``` + +### 10.7 Operating on Every Collection Element + +The `for_each` construct has the general form: + +``` + for_each ( identifier ; collection ) statement + +``` + +Currently, *collection* can only be a dynamic or fixed array or a list. The *identifier* is bound to each element of *collection* then the *statement* is evaluated. + +``` +// Outputs: "1 2 3 " \ +int[] nums = {1, 2, 3}; +for_each (x; nums) print(x + " "); + +``` + +### 10.8 Iteration on Collections over Indices + +The `for_index` construct has the general form: + +``` + for_index ( identifier [, idenitifer, ...] ; collection ) statement + +``` + +Currently, *collection* can be a dynamic or fixed array. The *identifier* is bound to each element of *collection* then the *statement* is evaluated. The number of identifiers must be the dimension of the collection. + +``` +// Outputs: "1 2 3 " \ +int[] nums = {1, 2, 3}; +for_index (i; nums) print(nums[i] + " "); + +``` + +For multidimensional arrays: + +``` +// Transpose a matrix \ +float[4,4] M = ...; +float[4,4] T; +for_index (i, j; M) T[i,j] = M[j,i]; + +``` + +### 10.9 Break and Continue: Short Circuiting Loops. + +All of the looping constructs can be “short-circuited” using `break` and `continue` . These function just like their C/C++ conterparts. break and continue will terminate the inner most loop. + +``` +while (true) +{ + string x = doit(); + if (x == "stop it") break; +} + +``` + +In the example above, the `while` loop will never terminate if `doit()` does not return “stop it”. + +If the loop as a *test-expression* , then continue can be used to cause the flow of control to skip the rest of the *statement* being iterated. In the case of a for loop, this will cause execution of the *tail-expr* before the *test-expr* . + +``` +// outputs: "1 2 " +for (int i=0; i < 10; i++) +{ + if (i > 2) continue; + print(i + " "); +} + +``` + +### 10.10 The throw statement + +The `throw` statement, which raises and exception, has two forms: + +``` +throw throw-expression +throw + +``` + +*throw-expression* can be of any reference type. So you cannot throw an `int` or `float` for example because these types are value types. + +The second form is applicable only inside a the catch clause of a try-catch statement. In that context, it rethrows the last thrown value. + +### 10.11 The try-catch statement + +The `try-catch` form is: + +``` +try +{ + *try-statements* +} +catch ( *catch-expression-1* ) +{ + *catch-statements* +} +catch ( *catch-expression-2* ) +{ + *catch-statements* +} +... +catch ( *catch-expression-N* ) +{ + *catch-statements* +} +catch (...) +{ + *default-catch-statements* +} + +``` + +The try section and one of the catch sections are mandatory. The use of a default catch statement — one whose *catch-expression* is ... — is optional. + +The statements in *try-statements* are evaluated. If during the course of evaluation an exception is raised by a throw statement, control will be returned to the try-catch state- ment. At that point a type match is attempted between the object thrown and each of the catch clauses of the try-catch statement. The first catch clause that matches has its *catch-statements* evaluated. The default catch statement — which has ... as its *catch- expression* will catch any type. + +An example best explains it: + +``` +try \ +{ + throw "no good!"; +} +catch (string s) +{ + print("caught " + s + "\n"); +} + +``` + +In this case the value “no good!” will be caught by the catch clause. The *catch-expression* in this case declares a varaible which is then assigned the thrown value. In the case of the default catch, you do not have access to the value: + +``` +try +{ + throw "no good!"; +} +catch (...) +{ + print("caught something!"); +} + +``` + +There is no way to identify the caught type, but clean up can occur. If you need to rethrow the value you can use throw with no arguments: + +``` +try +{ + throw "no good!"; +} +catch (...) +{ + print("caught something! rethrowing..."); + throw; +} + +``` + +This works for any type of catch clause. In addition, you can throw a completely different object if you need to: + +``` +try +{ + throw "no good!"; +} +catch (...) +{ + print("caught something!"); + throw "something else"; +} + +``` + +If no catch clause matches the thrown object type, its equivalent to a default catch that simply rethrows: + +``` +try +{ + something_that_throws(); +} +catch (...) +{ + throw; +} + +``` + +### 10.12 The assert() Function. ‌ + +Although the `assert()` function is not strictly part of Mu’s exception handling, the built-in function does provide a helpful way to debug run-time problems. + +`assert ( void; bool testexpr )` [Function] + +*testexpr* is evaluated. If the value is true, nothing happens. If the value is false, an exception is raised. The exception object will contain a string representation of the *testexpr* which can be output. + +For example the `mu` interpreter when present with this: + +``` +int x = 1; +assert(x == 2); + +``` + +will produce this: + +``` + ERROR: Uncaught Exception: Assertion failed: (== x 2). + +``` + +Currently, assert will produce a lisp-like expression indicating the failed *testexpr* . Note that if partial evaluation of the *testexpr* occurs, you may get a surprising result: + +``` +mu> assert(1 == 2); \ +ERROR: Uncaught Exception: Assertion failed: false. + +``` + +In the above case the *testexpr* was partially evaluated to false early in the compilation process. + +## 11. Namespace (Scoping) Rules ‌ + + +### 11.1 How Symbols are Assigned to Namespaces + +When a symbol is declared (like a variable or a function) it is declared in an existing *namespace* . A namespace is itself a symbol. For example functions, types, and modules are all namespaces. The namespaces form a hierarchy; the root is called the *global namespace* . The global namespace has no name, but is pointed to by the global symbol root (which is of course in the global namespace!). Normally you don’t need to access the global namespace by name. + +Since every namespace is in the global namespace, we can form absolute or relative paths to symbols. This makes it possible to disambiguate two symbols with the same name, but that live in different namespaces: + +``` +module: Foo +{ + global int i; +} +function: bar (void;) +{ + int i = 10; + print("%d\n" % (i + Foo.i)); +} + +``` + +In this example, in function bar we need to add two symbols named i. In order to indicate which one we are refering to the path to the global variable Foo.i is specifically given. + +### 11.2 Declaration Scopes and Rules + +Each of the following is a namespace in which symbols can be declared: + +* Modules. Modules are namespaces that can be accessed from any other namespace. Nested modules can access functions, variables, and types declared in the current mod- ule namespace or parent module namespaces without using a path to the symbol. +* Functions. Local variables, functions, modules, etc, that are declared inside a function body are only visible in the scope of that function or its child namespaces. It is not possible to reference a symbol inside a function (including its parameters) from outside of the function. +* Types. Fields of types are accessible to outside namespaces though the dot notation of objects. Global variables, functions, and types declared in the scope of a type are accessible from any namespace. Nested types follow the same scoping rules as nested modules (and can be intermixed with modules as well). This includes both record-like types (struct and class) as well as the union type. + +### 11.3 Loading a Module ‌ ‌ + +The `require` statement makes sure that a named module has been loaded. Code following a `require` statement is guaranteed to find symbols refered to in the specified module. + +``` + require module_name + +``` + +### 11.4 Using a namespace + +The use statement makes any accessible namespace visible *to the current scope* . + +``` + use *namespace* + +``` + +Once the current scope ends, the namespace referenced by use is no longer visible. This is a convenience to make code less verbose: + +``` +module: Foo +{ + function: add (int; int a, int b) { a + b; } +} + +use Foo; +add(1,2); ⇒ 3 + +``` + +In this case, the symbols in module Foo become visible to the current scope. So the function add can be directly refered to without calling it Foo.add. This also works for types: + +``` +class: Bar +{ + class: Thing { ... } +} + +use Bar; +Thing x = ...; + +``` + +Here the type Thing is directly visible becase Bar is being used. This also applies to any functions declared in Bar. + +#### 11.4.1 Using a Module Implies require + +The use statement not only makes a namespace visible, but can find namespaces on the filesystem before doing so. In other words, use can also perform the same function as `require` : + +``` +use io; +fstream file = fstream("x.tif"); + +``` + +Here the io module may be loaded if it was not already required by some other part of the program. use will attempt to load a module if it cannot find any namespace with the same name as the argument. When applied to modules, use can be thought of as doing two things: + +``` +require *module_name* +use *module_name* + +``` + +## 12. Symbol Aliasing ‌ + + +Aliasing allows you to assign an identifier as a stand-in for a symbol or constant expression. The scope an alias is the enclosing scope. An alias is a first class symbol, and can be referenced through the "." notation. Aliases are made using the binary infix ":=" operator. This operator creates an alias out of the name on the left hand side from the symbol or constant expression on the right hand side. + +``` + alias_symbol := existing_symbol_or_expression; + +``` + +### 12.1 Importing Symbols from Other Namespaces + +Aliasing is primarily a syntactic convenience. The most obivious use of aliasing is to import symbols from another namespace. For example, if you are using the "sin" function from the math module a lot, but do not which to use any other symbol in the math modules, you could do this: + +``` +require math; +sin := math.sin; + +``` + +This effectively imports only the sin function into the current scope. Similarily if there is a module which is buryed deep within other namespaces, you can pull the module name into the current scope like this: + +``` +require some.very.far.away.module; +module := some.very.far.away.module; +module.call_some_function(); + +``` + +Aliases can also be assigned to other aliases. So for example: + +``` +require math; +sin := math.sin; +cos := sin; +tan := cos; + +sin(123.321) == tan(123.321); // eek! that’s true! + +``` + +This example "imports" the sin function into the current namespace and then maliciously calls it "cos" and "tan" as well. Chaos ensues. + +### 12.2 Function Aliasing + +You can alias function names. There are a couple instances where this becomes useful. The first is when a function name or a path to a function name becomes unweildy: + +``` +f := doSomeIncrediblyHeinousThingToAFloatingPointNumber; +float x = f(123.0); + +``` + +In addition, you can use function aliasing to change a member function into a normal function: + +``` +append := float[].push_back; +float[] array; +append(array, 123.0); + +``` + +In the above example, the push back() member function is aliased to the name "append". The member function can then be called as a normal function. Note that the aliasing does not resolve virtual functions, it grabs the exact function specified on the right hand side. In addition, when a function name is overloaded, the alias represents all the overloaded functions. In effect, the alias is overloaded the same way the function name is. This is particularly evident in type aliasing; the alias not only represents a type name, but all constuctors for the type as well. + +### 12.3 Type Aliasing + +Perhaps the most usefull form of aliasing is type aliasing. This is essentially the same as the C language "typedef" statement. In Mu, to create a type alias you assign a type name to an identifier: + +``` + Scary := float[][100][50,123][][]; + +``` + +you can then use the "Scary" symbol in place of its alias: + +``` + Scary foo = Scary(); + +``` + +This can be particularily useful in cases where the meaning of array types becomes messy. Here’s a very contrived example + +``` +hpoint := float[4]; // a point is a homogeneous 4d object +cv := vector hpoint; // a cv is a vector point +Patch := cv[4,4]; // a Patch is a 4x4 array of cvs +Surface := Patch[,]; // a Surface is resizable 2D array of Patches +Model := Surface[]; // A Model is a collection of Surfaces +Scene := Model[]; // A Scene is a collection of Models + +``` + +When compiling, Mu will substitute symbols for their aliases recursively until a type is found. In the above case, the Scene alias expands out to: + +``` + Scene := (vector float[4])[4,4][,][][]; + +``` + +The standard modules use type aliasing. The math module declares some type aliases for vector types: + +``` +math.vec4f := vector float[4]; +math.vec3f := vector float[3]; +math.vec2f := vector float[2]; + +``` + +### 12.4 Variable Aliasing + +Finally, a less useful form of aliasing is varible name aliasing. Again, the syntax is simple assignment to an identifier: + +``` +float foo = 123.321; +bar := foo; +print(bar + "\n"); + +``` + +Note the similarity to the following: + +``` +float foo = 123.321; +float bar = foo; +print(bar + "\n"); + +``` + +Both examples produce the same output, but syntactically two very different things are happening. In the first example, an alias to the variable foo is created called bar. Bar is not a new float variable; it is a second name for the variable foo. In the second example, an actual location in memory is created called bar and that second variable is assigned the value of foo. + +### 12.5 Symbolic Constants + +The symbol aliasing syntax can also be used to declare a symbolic constant. This is roughly equivalent to a macro in C. The symbolic constant will always reduce to a constant expres- sion when used elsewhere. + +``` + pi := 3.14; + +``` + +The symbol `pi` is not a variable and so cannot have its value changed. + +## 13. Separate Parse and Compilation Modules ‌ + + +### 13.1 Module Definition + +### 13.2 File System Locations + +### 13.3 Module as a Unit of Compilation + +### 13.4 Different Flavors of Module + +### 13.5 Loading Modules at Runtime + +## 14. Documenting Source Code ‌ + + +Mu has built-in syntax for annotating source code symbols like functions, variables, and type definitions. When parsed and compiled the documentation is assigned to a symbol and can be retrieved at runtime along with other symbol information. (See Runtime Module). + +Modules like autodoc can convert the documentation into various formats like plain ASCII, HTML, or a TEX dialect. (See Autodoc Module). + +### 14.1 Source Code Comments + +Mu uses C++ comment syntax + +A double slash // comments to the end of the line. + +A slash followed by a star /* begins a comment that may include newlines. The comment is terminated by */. + +### 14.2 Compiled Documentation + +A documentation statement starts with the documentation: keyword followed by a string constant: + +``` + documenation: "documentation string goes here"; + +``` + +The parser will cache the documentation string until a symbol is defined in the same scope as the string. At the point, the parser will assign the documentation string to that symbol. If the scope changes, the documentation string may become orphaned. + +An alternative more specific syntax makes it possible to specify the name of the next symbol to attach the string to: + +``` + documentation: foo "documentation string goes here"; + +``` + +In this case the next symbol defined as foo in the same scope as the documentation will be assigned the string. This makes it possible to string a number of documentation statements together before a compound definition (like a function) and document each symbol involved in the definition. This can be especially useful for documenting function parameters. For example: + +``` + documentation: rotate +"""The rotate function transforms a vector about the origin returning the transformed vector."""; + +documentation: axis +"""The axis about which to rotate"""; + +documentation: radians +"""The amount to rotate about the axis in radians"""; + +documentation: v +"""The vector to rotate"""; + +\: rotate (vector float[3]; +vector float[3] v, vector float[3] axis, vector float[3] radians) +{ +.. +} + +``` + +### 14.3 Storage of Compiled Documentation + +### 14.4 Documenation Syntax + +## 15. Memory Management ‌ + + +Objects in Mu are automatically allocated and destroyed by the runtime environment. Objects created by the program, stack objects, and temporary objects in expressions are all handled in the same way. The Mu runtime environment uses a stanrdard mark-sweep garbage collection algorithm to find objects that are no longer being used and queues them up for finalization. When the objects are finalized, destructors are run and the object is reclaimed. + +### 15.1 Allocation + +There are three ways that objects are allocated: variable initialization, a constructor call, or as a temporary object during expression evaluation. When declaring a variable, the initialization either occurs directly or indirectly: + +``` +string foo = string(10); // foo = "10" +string bar; // bar = "" + +``` + +By default, lack of an initialization expression causes Mu to supply the default construc- tor. In the above example, the "bar" variable is initialized like this: + +``` + string bar = string(); + +``` + +Object variables are never set to nil unless you explicitly do so: + +``` + string baz = nil; + +``` + +The variable baz points to nothing. If you attempt to call a member function on baz, an exception will be thrown: + +``` + baz.size(); // throws! baz == nil + +``` + +### 15.2 Deallocation + +Mu uses garbage collection to reclaim unused memory. There are never dangling references in a Mu program. When a certain amount of time has passed, or a certain number of objects have been allocated, the runtime environment may invoke the garbage collector. Obviously, this has consequences on program design. You cannot rely on objects being finalized at a particular point in a program. + +The garbage collector can be tuned from the runtime module: + +``` +require runtime; +runtime.collect(); // invoke garbage collector manually +runtime.set_collection_threshold(1000); // set object overhead parameter + +``` + +## 16. Closures and Partial Evaluation ‌ + + +### 16.1 Closures + +Closures serve two purposes in Mu. One is to wrap variable references in nested functions. The other is to wrap some function arguments to mimic partial evaluation. + +When nested functions are used, + +``` +function: foo (int; int a, int b) +{ + function: bar (int; int c) { a * c; } + bar(3.14) + b; +} + +``` + +The `foo` function above will return `a * 3.14 + b` . The function bar is referring to a variable a in its scope. In essense, bar really has two arguments: c and another argument. Whenever bar is called, the extra argument’s value is understood to be the value of the variable a. This is essentially syntactic sugar. + +In this case, the closure is created whenever the function with hidden arguments is called. At the call point a closure object (which is a type of function object) is created that stores some of the parameter values. The closure object appears to be a function itself albeit with few arguments. The closure can be used anywhere a normal function object can be used. + +In the second case, a closure can be directly created. For example let’s say we defined a function that prints a message: + +``` +function: show_message (void; string msg) +{ + print("The message is: " + msg + "\n"); +} + +``` + +Now let’s say there is a UI button object which will call a function with no arguments and no return value (type of (void;)). Since the show_message function has an argument of type string, it cannot be passed to the button: + +``` +button b = ...; +b.set_callback(show_message); // error wrong type + +``` + +However, let’s say we just want a message to be printed when the button is pressed. We could do this: + +``` +function: adapter (void;) +{ + show_message("my message"); +} + +b.set_callback(adapter); // ok + +``` + +But we had to write an extra function. We could alternately use an anonymous function like this: + +``` + b.set_callback(\: (void;) { show_message("my_message"); }); + +``` + +but that’s not too much better. Finally, we can create a closure around the show_message function by only partially applying it: + +``` + b.set_callback(closure(show_message, "my message")); // ok + +``` + +In this case, the `closure()` function generated a closure object that appears as a function of type (void;). + +### 16.2 Constant Expressions + +Mu evaluates expressions very aggressively. If the compiler/interpreter can find that an expression is constant, it will evaluate it immediately. This may occur before an expression is even finished parsing. + +This is not unlike C/C++ compilers evaluating constant expressions at compile time. + +In addition, all functions are tagged with information about possible side effects or lack thereof. Because of this, even functions in modules can be evaluated early in order to fold constant values. Here’s an example: + +``` + float f = math.acos(math.cos(math.pi) * -1.0); + +``` + +the interpreter reports that this evaluates to the runtime expression: + +``` + float f = 0.0; + +``` + +During parsing the interpreter determined that math.cos and math.acos are side effect free and therefor a constant argument will produce a constant return value. This is one form of partial evaluation. + +### 16.3 Explicit Partial Application + +Functions may be partially evaluated (applied) explicitly by using the empty argument syntax. This only applies to functions which take more than one argument. + +The result of a partial function evaluation is a new function which will have the same return type as the partially evaluated function but a subset of its arguments. The following interactive session provides a basic example: + +``` +mu> \: add (int; int a, int b) { a + b } +mu> add(1,); // NOTE: missing arg +(int;int) => lambda (int; int) (+ 1 lambda.a) +mu> add(1,)(10); +float => 11.0f +mu> add(1,10) +float => 11.0f + +``` + +Notice that when you call the function add with all its arguments it does what you expect: returns an `int` . When you call with *some* of its arguments it returns a new function. + +### 16.4 Explicit Partial Evaluation + +## 17. Phases ‌ + + +Mu has three phases: parse, compilation, and runtime evaluation. + +### 17.1 Parse Phase + +### 17.2 Compilation Phase + +### 17.3 Runtime Phase + +## 18. Muc: Mu Compiler ‌ + + +### 18.1 Invocation + +### 18.2 Options + +### 18.3 Muc Target + +### 18.4 C++ Target + +## 19. Standard Library ‌ + + +### 19.1 Built-in Functions + +Printing Values String Formating size + +### 19.2 Runtime Module + +### 19.3 Autodoc Module + +### 19.4 Math Modules + +### 19.5 I/O Module + +serialize + +### 19.6 OpenGL Related Modules + +### 19.7 POSIX Functions + +### 19.8 Image I/O, Storage, and Operations + +### 19.9 OpenCV Functions and Data Types + +### 19.10 Read/Write GTO Files + +## 20. Mu Compared to Similar Languages ‌ + + +### 20.1 C++ + +### 20.2 Python + +### 20.3 JavaScript + +### 20.4 ML Family of Languages + +## 21. Example Usage ‌ + + +### 21.1 Embedding Mu in a C++ Application + +### 21.2 Using Mu as a Shading Language + +### 21.3 Using Mu to Control Particle Dynamics + +### 21.4 Mu by Itself + +## Appendix A Reference ‌ + + +### Properties + +(Index is nonexistent) + +### Functions + +### A + +[assert](#1012-the-assert-function-) + +### C + +[cons](#311-cons) + +### H + +[head](#312-head-and-tail) + +### T + +[tail](#312-head-and-tail) + +### Types + +(Index is nonexistent) \ No newline at end of file diff --git a/doc/rv-manuals/rv-opencolorio-integrations.md b/doc/rv-manuals/rv-opencolorio-integrations.md new file mode 100644 index 000000000..51276da9b --- /dev/null +++ b/doc/rv-manuals/rv-opencolorio-integrations.md @@ -0,0 +1,75 @@ +# OpenColorIO Development Integration Notes + +## OpenColorIO Development Integration Notes + +RV has new OpenColorIO (OCIO) integration. + +You can use OCIO nodes in addition to or in place of RV’s "native" nodes in the Linearize, Color, Look, and Display pipelines. In each case OCIO can be used to convert from an incoming and outgoing color space with a user defined OCIO context. OCIO requires some work to set up and should be considered an advanced feature. Large facilities may find OCIO particularily useful in RV when used in conjunction with Nuke, Mari, or other products which support it. + +You can learn about OpenColorIO [here](http://opencolorio.org) . + +In order to use OCIO with RV a source_setup package needs to be created along with OCIO configuration files, LUTs, and an appropriate user environment. RV comes with a sample OCIO package which can be enabled via the preferences. When the OCIO source setup package is enabled, parts or all of RV’s existing color pipelines may be replaced with OCIO equivalents. Note that the sample OCIO source setup package **supplements** the default source_setup and does not replace it; there is no need to turn off the default source_setup. We highly recommend copying and customizing the sample OCIO package for real world use. + +### Testing the Sample OCIO Source Setup + +1. Find the "OpenColorIO Basic Color Management" Package in the Packages tab of the Preferences. Click "Load" button next to the package and exit. + +2. Set the "OCIO" environment variable to the path to your favorite OCIO config file: + + ``` + setenv OCIO /OCIO/spi-vfx/config.ocio + ``` + +3. Start RV and load an image with the color space in the name or otherwise (however is appropriate for your config). + + ``` + rv /media/images/ocio_special_names/marcie_clean_lg10.cin + ``` + +4. Note the "OCIO" top-level menu that appears when you use RV this way. You can use this menu to chose a Linearizing transform, or Display transform, from those provided by your config. + + +### Operation of the OCIO Node + +For the initial release we’re seeking feedback about how best to use OCIO in RV. To facilitate that, the current version of the OCIO node is configured to emulate three of the OCIO nuke node types: color, look, and display. There are additional OCIO nuke nodes (file transform, CDL transform, log convert) which we could also add to that list if we receive feedback indicating that’s necessary. + +The OCIO nodes can be used in any of the RV pipelines as either a supplement to RV’s existing color pipeline or in place of it. The pipelines are placed in RV’s node graph in the places where all of the significant color processing occurs. By default these pipelines contain RV’s color nodes (RVLinearize, RVColor, RVLookLUT, RVDisplayColor). + +The default OCIO package will swap in OCIO equivalent nodes for the existing RV nodes. For example when using the SPI OCIO config with an "lg10" cineon file the OCIO package will remove RV’s RVLinearize node in the linearization pipeline and replace it with a OCIOFile node set to take the lg10 input by default and output scene referred linear. The default OCIO package will also swap out the RVDisplayColor node (which does display color correction) for an OCIODisplay node. The OCIODisplay node is set to take scene referred linear and output to the default viewing transform (by default). + +There are four OCIO node types: OCIONode, OCIOFile, OCIODisplay, and OCIOLook. All of them have the same properties; they only differ in their default configuration. Each of them can be used in any context if desired. The different node type names are primarily to make it easy to identify the nodes from the user interface. + +The generic OCIONode can used as a top level (user) node. As with the RVColor node the OCIONode can therefor be used as a secondary color correction. + +Note that its perfectly reasonable to use both RV and OCIO nodes in any of the pipelines. However, the example package does not do this: the intention is to show how OCIO can be used (at least for some media) *in place of* RV’s color pipeline. + +Table 1. OCIO Node Properties + +| | | +| --- | --- | +| string ocio.function | One of "color", "look", or "display" | +| float ocio.lut | Used internally to store the OCIO generated 3D LUT | +| int ocio.active | Activates/deactivates the OCIO node | +| int ocio.lut3DSize | The desired size of the OCIO generated 3D LUT (default=32) | +| string ocio.inColorSpace | The OCIO name of the input color space | +| string ocio_color.outColorSpace | The OCIO name of the output color space when ocio.function == "color" | +| string ocio_look.look | The OCIO command string for the look when ocio.function == "look" | +| int ocio_look.direction | 0=forward, 1=inverse | +| string ocio_display.display | OCIO display name when ocio.function == "display" | +| string ocio_display.view | OCIO view name when ocio.function == "display" | +| component ocio_context | String properties in this component become OCIO config name/value pairs | + +### The ocio_context Component + +You can add properties to the OCIO node in RV to create an OCIO context. Any string property in the component called `ocio_context` will become a name/value pair for the OCIO context. + +### Feedback + +* Should individual slots be hard coded to similar nuke node types? E.g. should the look OCIO slot behave only as if ocio.function == "look"? Or should we leave it up to the user to decide? + +* We can add control for constructing a display pipeline like ociodisplay has (gamma, exposure, etc). Although this generally overlaps with existing RV features it may help for matching color between applications. + + +* * * + +Last updated 2019-02-22 13:40:28 EST \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual.md b/doc/rv-manuals/rv-reference-manual.md new file mode 100644 index 000000000..c2c33259a --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual.md @@ -0,0 +1,19 @@ +# Open RV Reference Manual + +* [Chapter 1 - Overview](rv-reference-manual/rv-reference-manual-chapter-one.md) +* [Chapter 2 - Image Processing Graph](rv-reference-manual/rv-reference-manual-chapter-two.md) +* [Chapter 3 - Writing a Custom GLSL Node](rv-reference-manual/rv-reference-manual-chapter-three.md) +* [Chapter 4 - Python](rv-reference-manual/rv-reference-manual-chapter-four.md) +* [Chapter 5 - Event Handling](rv-reference-manual/rv-reference-manual-chapter-five.md) +* [Chapter 6 - Open RV File Format](rv-reference-manual/rv-reference-manual-chapter-six.md) +* [Chapter 7 - Using Qt in Mu](rv-reference-manual/rv-reference-manual-chapter-seven.md) +* [Chapter 8 - Modes and Widgets](rv-reference-manual/rv-reference-manual-chapter-eight.md) +* [Chapter 9 - Package System](rv-reference-manual/rv-reference-manual-chapter-nine.md) +* [Chapter 10 - A Simple Package](rv-reference-manual/rv-reference-manual-chapter-ten.md) +* [Chapter 11 - The Custom Matte Package](rv-reference-manual/rv-reference-manual-chapter-eleven.md) +* [Chapter 12 - Automated Color and Viewing Management](rv-reference-manual/rv-reference-manual-chapter-twelve.md) +* [Chapter 13 - Network Communication](rv-reference-manual/rv-reference-manual-chapter-thirteen.md) +* [Chapter 14 - Webkit JavaScript Integration](rv-reference-manual/rv-reference-manual-chapter-fourteen.md) +* [Chapter 15 - Hierarchical Preferences](rv-reference-manual/rv-reference-manual-chapter-fifteen.md) +* [Chapter 16 - Node Reference](rv-reference-manual/rv-reference-manual-chapter-sixteen.md) +* [Chapter 17 - Additional GLSL Node Reference](rv-reference-manual/rv-reference-manual-chapter-seventeen.md) \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-eight.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-eight.md new file mode 100644 index 000000000..eccdd01d4 --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-eight.md @@ -0,0 +1,83 @@ +# Chapter 8 - Modes and Widgets + +The user interface layer can augment the display and event handling in a number of different ways. For display, at the lowest level it's possible to intercept the render event in which case you override all drawing. Similarily for event handling you can bind functions in the global event table possibly overwriting existing bindings and thus replace their functions.At a higher level, both display and event handling can be done via Modes and Widgets. A Mode is a class which manages an event table independent of the global event table and a collection of functions which are bound in that table. In addition the mode can have a render function which is automatically called at the right time to augment existing rendering instead of replacing it. The UI has code which manages modes so that they may be loaded externally only when needed and automatically turned on and off.Modes are further classified as being minor or major. The only difference between them is that a major mode will always get precedence over any minor mode when processing events and there can be only a single major mode active at a time. There can be many minor modes active at once. Most extensions are created by creating a minor mode. RV currently has a single basic major mode. + +![13_lease_event_prop.png](../../images/rv-reference-manual-13-rv-cx-lease-event-prop-12.png) + +Figure 8.1:Event Propagation. Red and Green modes process the event. On the left the Red mode rejects the event allowing it to continue. On the right Red mode does not reject the event stopping the propagation.By using a mode to implement a new feature or replace or augment an existing feature in RV you can keep your extensions separate from the portion of the UI that ships with RV. In other words, you never need to touch the shipped code and your code will remain isolated.A further refinement of a mode is a widget. Widgets are minor modes which operate in a constrained region of the screen. When the pointer is in the region, the widget will receive events. When the pointer is outside the region it will not. Like a regular mode, a widget has a render function which can draw anywhere on the screen, but usually is constrainted to its input region. For example, the image info box is a widget as is the color inspector.Multiple modes and widgets may be active at the same time. At this time Widgets can only be programmed using Mu. + +### 8.1 Outline of a Mode + + +In order to create a new mode you need to create a module for it and derive your mode class from the MinorMode class in the rvtypes module. The basic outline which we'll put in a file called new_mode.mu looks like this: + +``` + use rvtypes; + +module: new_mode { + +class: NewMode : MinorMode +{ + method: NewMode (NewMode;) + { + init ("new-mode", + [ global bindings ... ], + [ local bindings ... ], + Menu(...) ); + } +} + +\: createMode (Mode;) +{ + return NewMode(); +} + +} // end of new_mode module +``` +The function createMode() is used by the mode manager to create your mode without knowing anything about it. It should be declared in the scope of the module (not your class) and simply create your mode object and initialize it if that's necessary.When creating a mode it's necessary to call the init() function from within your constructor method. This function takes at least three arguments and as many as six. Chapter [10](rv-reference-manual-chapter-ten.md#chapter-10-a-simple-package) goes into detail about the structure in more detail. It's declared like this in rvtypes.mu: + +``` + method: init (void; + string name, + BindingList globalBindings, + BindingList overrideBindings, + Menu menu = nil, + string sortKey = nil, + int ordering = 0) +``` +The name of the mode is meant to be human readable.The “bindings” arguments supply event bindings for this mode. The bindings are only active when the mode is active and take precedence over any “global” bindings (bindings not associated with any mode). In your event function you can call the “reject” method on an event which will cause rv to pass it on to bindings “underneath” yours. This technique allows you to augment an existing binding instead of replacing it. The separation of the bindings into overrideBindings and globalBindings is due to backwards compatibility requirements, and is no longer meaningful.The menu argument allows you to pass in a menu structure which is merged into the main menu bar. This makes it possible to add new menus and menu items to the existing menus.Finally the sortKey and ordering arguments allow fine control over the order in which event bindings are applied when multiple modes are active. First the ordering value is checked (default is 0 for all modes), then the sortKey (default is the mode name).Again, see chapter [10](rv-reference-manual-chapter-ten.md#chapter-10-a-simple-package) for more detailed information. + +### 8.2 Outline of a Widget + + +A Widget looks just like a MinorMode declaration except you will derive from Widget instead of MinorMode and the base class init() function is simpler. In addition, you'll need to have a render() method (which is optional for regular modes). + +``` + use rvtypes; + +module: new_widget { + +class: NewWidget : Widget +{ + method: NewWidget (NewWidget;) + { + init ("new-widget", + [ local bindings ... ] ); + } + + method: render (void; Event event) + { + ... + updateBounds(min_point, max_point); + ... + } +} + +\: createMode (Mode;) +{ + return NewWidget(); +} + +} // end of new_widget module +``` +In the outline above, the function updateBounds() is called in the render() method. updateBounds() informs the UI about the bounding box of your widget. This function must be called by the widget at some point. If your widget can be interactively or procedurally moved, you will probably want to may want to call it in your render() function as shown (it does not hurt to call it often). The min_point and max_point arguments are Vec2 types. \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-eleven.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-eleven.md new file mode 100644 index 000000000..d68c6c5a2 --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-eleven.md @@ -0,0 +1,521 @@ +# Chapter 11 - The Custom Matte Package + +Now that we've tried the simple stuff, let's do something useful. RV has a number of settings for viewing mattes. These are basically regions of the frame that are darkened or completely blackened to simulate what an audience will see when the movie is projected. The size and shape of the matte is an artistic decision and sometimes a unique matte will be required. + +You can find various common mattes already built into RV under the View menu. In this example we'll create a Python package that reads a file when RV starts to get a list of matte geometry and names. We'll make a custom menu out of these which will set some state in the UI.To start with, we'll assume that the path to the file containing the mattes is located in an environment variable called RV_CUSTOM_MATTE_DEFINITIONS. We'll get the value of that variable, open and parse the file, and create a data struct holding all of the information about the mattes. If it is not defined we will provide a way for the user to locate the file through an open-file-dialog and then parse the file. + +### 11.1 Creating the Package + + +Use the same method described in Chapter [10](rv-reference-manual-chapter-ten.md#chapter-10-a-simple-package) to begin working on the package. If you haven't read that chapter please do so first. A completed version of the package created in this chapter is included in the RV distribution. So using that as reference is a good idea. + +### 11.2 The Custom Matte File + + +The file will be a very simple comma separated value (CSV) file. Each line starts with the name of the custom matte (shown in the menu) followed by four floating point values and then a text field description which will be displayed when that matte is activated. So each line will look something like this: + +``` + matte menu name, aspect ratio, fraction of image visible, center point of matte in X, center point of matte in Y, descriptive text +``` + +### 11.3 Parsing the Matte File + + +Before we actually parse the file, we should decide what we want when we're done. In this case we're going to make our own data structure to hold the information in each line of the file. We'll hold all of the information we collect in a Python dictionary with the following keys: + +``` + "name", "ratio", "heightVisible", "centerX", "centerY", and "text" +``` +Next we'll write a method for our mode that does the parsing and updates our internal mattes dictionary. + +> **Note:** If you are unfamiliar with object oriented programing you can substitute the word function for method. This manual will sometimes refer to a method as a function. It will never refer to a non-method function as a method. + +``` + def updateMattesFromFile(self, filename): + # Make sure the definition file exists + if (not os.path.exists(filename)): + raise KnownError("ERROR: Custom Mattes Mode: Non-existent mattes" + + " definition file: '%s'" % filename) + + # Walk through the lines of the definition file collecting matte + # parameters + order = [] + mattes = {} + for line in open(filename).readlines(): + tokens = line.strip("\n").split(",") + if (len(tokens) == 6): + order.append(tokens[0]) + mattes[tokens[0]] = { + "name" : tokens[0], "ratio" : tokens[1], + "heightVisible" : tokens[2], "centerX" : tokens[3], + "centerY" : tokens[4], "text" : tokens[5]} + + # Make sure we got some valid mattes + if (len(order) == 0): + self._order = [] + self._mattes = {} + raise KnownError("ERROR: Custom Mattes Mode: Empty mattes" + + " definition file: '%s'" % filename) + + self._order = order + self._mattes = mattes +``` +There are a number of things to note in this function. First of all, to keep track of the order in which we read the definitions from the mattes file you will see that stored in the “_order” Python list. The “_mattes” dictionary's keys are the same as the “_order” list, but since dictionaries are not ordered we use the list to remember the order.We check to see if the file actually exists and if not simply raise a KnownError Exception. So the caller of this function will have to be ready to except a KnownError if the matte definition file cannot be found or if it is empty. The KnowError Exception is simply our own Exception class. Having our own Exception class allows us to raise and except Exceptions that we know about while letting others we don't expect to still reach the user. Here is the definition of our KnownError Exception class. + +``` + class KnownError(Exception): pass +``` +We use the built-in Python readlines() method to go through the mattes file contents one line at a time. Each time through the loop, the next line is split over commas since that's how defined the fields of each line.If there are not exactly 6 tokens after splitting the line, that means the line is corrupt and we ignore it. Otherwise, we add a new dictionary to our “_mattes” dictionary of matte definition dictionaries.If we cannot find the path defined in the environment variable then we leave it blank: + +``` + try: + definition = os.environ["RV_CUSTOM_MATTE_DEFINITIONS"] + except KeyError: + definition = "" +``` +At this point the custom_mattes.py file looks like this: + +``` +from rv import commands, rvtypes +import os + +class KnownError(Exception): pass + +class CustomMatteMinorMode(rvtypes.MinorMode): + + def __init__(self): + rvtypes.MinorMode.__init__(self) + self._order = [] + self._mattes = {} + self._currentMatte = "" + self.init("custom-mattes-mode", None, None, None) + + try: + definition = os.environ["RV_CUSTOM_MATTE_DEFINITIONS"] + except KeyError: + definition = "" + try: + self.updateMattesFromFile(definition) + except KnownError,inst: + print(str(inst)) + + def updateMattesFromFile(self, filename): + + # Make sure the definition file exists + if (not os.path.exists(filename)): + raise KnownError("ERROR: Custom Mattes Mode: Non-existent mattes" + + " definition file: '%s'" % filename) + + # Walk through the lines of the definition file collecting matte + # parameters + order = [] + mattes = {} + for line in open(filename).readlines(): + tokens = line.strip("\n").split(",") + if (len(tokens) == 6): + order.append(tokens[0]) + mattes[tokens[0]] = { + "name" : tokens[0], "ratio" : tokens[1], + "heightVisible" : tokens[2], "centerX" : tokens[3], + "centerY" : tokens[4], "text" : tokens[5]} + + # Make sure we got some valid mattes + if (len(order) == 0): + self._order = [] + self._mattes = {} + raise KnownError("ERROR: Custom Mattes Mode: Empty mattes" + + " definition file: '%s'" % filename) + + self._order = order + self._mattes = mattes + +def createMode(): + return CustomMatteMinorMode() +``` + +### 11.4 Adding Bindings and Menus + + +The mode constructor needs to do three things: call the file parsing function, do something sensible if the matte file parsing fails, and build a menu with the items found in the matte file as well as add bindings to the menu items.We have already gone over the parsing. Once parsing is done we either have a good list of mattes or an empty one, but either way we move on to setting up the menus. Here is the method that will build the menus and bindings. + +``` + def setMenuAndBindings(self): + + # Walk through all of the mattes adding a menu entry as well as a + # hotkey binding for alt + index number + # NOTE: The bindings will only matter for the first 9 mattes since you + # can't really press "alt-10". + matteItems = [] + bindings = [] + if (len(self._order) > 0): + matteItems.append(("No Matte", self.selectMatte(""), "alt `", + self.currentMatteState(""))) + bindings.append(("key-down--alt--`", "")) + + for i,m in enumerate(self._order): + matteItems.append((m, self.selectMatte(m), + "alt %d" % (i+1), self.currentMatteState(m))) + bindings.append(("key-down--alt--%d" % (i+1), m)) + else: + def nada(): + return commands.DisabledMenuState + matteItems = [("RV_CUSTOM_MATTE_DEFINITIONS UNDEFINED", + None, None, nada)] + + # Always add the option to choose a new definition file + matteItems += [("_", None)] + matteItems += [("Choose Definition File...", self.selectMattesFile, + None, None)] + + # Clear the menu then add the new entries + matteMenu = [("View", [("_", None), ("Custom Mattes", None)])] + commands.defineModeMenu("custom-mattes-mode", matteMenu) + matteMenu = [("View", [("_", None), ("Custom Mattes", matteItems)])] + commands.defineModeMenu("custom-mattes-mode", matteMenu) + + # Create hotkeys for each matte + for b in bindings: + (event, matte) = b + commands.bind("custom-mattes-mode", "global", event, + self.selectMatte(matte), "") +``` +You can see that creating the menus and bindings walks through the contents of our “_mattes” dictionary in the order dictated by “_order”. If there are no valid mattes found then we add the alert in the menu to the user that the environment variable was not defined. You can also see from the example above that each menu entry is set to trigger a call to selectMatte for the associated matte definition. This is a neat technique where we use a factory method to create our event handling method for each valid matte we found. Here is the content of that: + +``` + def selectMatte(self, matte): + + # Create a method that is specific to each matte for setting the + # relevant session node properties to display the matte + def select(event): + self._currentMatte = matte + if (matte == ""): + commands.setIntProperty("#Session.matte.show", [0], True) + extra_commands.displayFeedback("Disabling mattes", 2.0) + else: + m = self._mattes[matte] + commands.setFloatProperty("#Session.matte.aspect", + [float(m["ratio"])], True) + commands.setFloatProperty("#Session.matte.heightVisible", + [float(m["heightVisible"])], True) + commands.setFloatProperty("#Session.matte.centerPoint", + [float(m["centerX"]), float(m["centerY"])], True) + commands.setIntProperty("#Session.matte.show", [1], True) + extra_commands.displayFeedback( + "Using '%s' matte" % matte, 2.0) + return select +``` +Notice that we didn't say which matte to set it to. The function just sets the value to whatever its argument is. Since this function is going to be called when the menu item is selected it needs to be an event function (a function which takes an Event as an argument and returns nothing). In the case where we want no matte drawn, we'll pass in the empty string (“”).The menu state function (which will put a check mark next to the current matte) has a similar problem. In this case we'll use a mechanism with similar results. We'll create a method which returns a function given a matte. The returned function will be our menu state function. This sounds complicated, but it's simple in use:The thing to note here is that the parameter m passed into currentMatteState() is being used inside the function that it returns. The m inside the matteState() function is known as a free variable. The value of this variable at the time that currentMatteState() is called becomes wrapped up with the returned function. One way to think about this is that each time you call currentMatteState() with a new value for m, it will return a different copy of matteState() function where the internal m is replaced the value of currentMatteState()'s m. + +``` + def currentMatteState(self, m): + def matteState(): + if (m != "" and self._currentMatte == m): + return commands.CheckedMenuState + return commands.UncheckedMenuState + return matteState +``` +Selecting mattes is not the only menu option we added in setMenuAndBindings(). We also added an option to select the matte definition file (or change the selected one) if none was found before. Here is the contents of the selectMatteFile() method: + +``` + def selectMattesFile(self, event): + definition = commands.openFileDialog(True, False, False, None, None)[0] + try: + self.updateMattesFromFile(definition) + except KnownError,inst: + print(str(inst)) + self.setMenuAndBindings() +``` +Notice here that we basically repeat what we did before when parsing the mattes definition file from the environment. We update our internal mattes structures and the setup the menus and bindings.It is also important to clear out any existing bindings when we load a new mattes file. Therefore we should modify our parsing function do this for us like so: + +``` + def updateMattesFromFile(self, filename): + + # Make sure the definition file exists + if (not os.path.exists(filename)): + raise KnownError("ERROR: Custom Mattes Mode: Non-existent mattes" + + " definition file: '%s'" % filename) + + # Clear existing key bindings + for i in range(len(self._order)): + commands.unbind( + "custom-mattes-mode", "global", "key-down--alt--%d" % (i+1)) + + ... THE REST IS AS BEFORE ... +``` +So the full mode constructor function now looks like this: + +``` + class CustomMatteMinorMode(rvtypes.MinorMode): + + def __init__(self): + rvtypes.MinorMode.__init__(self) + self._order = [] + self._mattes = {} + self._currentMatte = "" + self.init("custom-mattes-mode", None, None, None) + + try: + definition = os.environ["RV_CUSTOM_MATTE_DEFINITIONS"] + except KeyError: + definition = "" + try: + self.updateMattesFromFile(definition) + except KnownError,inst: + print(str(inst)) +``` + +### 11.5 Handling Settings + + +Wouldn't it be nice to have our package remember what our last matte setting was and where the last definition file was? Lets see how to add settings. First thing is first. We need to write our settings in order to read them back later. Lets start by writing out the location of our mattes definition file when we parse a new one. Here is an updated version of updateMattesFromFile(): + +``` + def updateMattesFromFile(self, filename): + + # Make sure the definition file exists + if (not os.path.exists(filename)): + raise KnownError("ERROR: Custom Mattes Mode: Non-existent mattes" + + " definition file: '%s'" % filename) + + # Clear existing key bindings + for i in range(len(self._order)): + commands.unbind( + "custom-mattes-mode", "global", "key-down--alt--%d" % (i+1)) + + # Walk through the lines of the definition file collecting matte + # parameters + order = [] + mattes = {} + for line in open(filename).readlines(): + tokens = line.strip("\n").split(",") + if (len(tokens) == 6): + order.append(tokens[0]) + mattes[tokens[0]] = { + "name" : tokens[0], "ratio" : tokens[1], + "heightVisible" : tokens[2], "centerX" : tokens[3], + "centerY" : tokens[4], "text" : tokens[5]} + + # Make sure we got some valid mattes + if (len(order) == 0): + self._order = [] + self._mattes = {} + raise KnownError("ERROR: Custom Mattes Mode: Empty mattes" + + " definition file: '%s'" % filename) + + # Save the definition path and assign the mattes + commands.writeSettings( + "CUSTOM_MATTES", "customMattesDefinition", filename) + self._order = order + self._mattes = mattes +``` +See how at the bottom of the function we are now writting the definition file to the CUSTOM_MATTES settings. Now lets also update the selectMatte() method to remember what matte we selected. + +``` + def selectMatte(self, matte): + + # Create a method that is specific to each matte for setting the + # relevant session node properties to display the matte + def select(event): + self._currentMatte = matte + if (matte == ""): + commands.setIntProperty("#Session.matte.show", [0], True) + extra_commands.displayFeedback("Disabling mattes", 2.0) + else: + m = self._mattes[matte] + commands.setFloatProperty("#Session.matte.aspect", + [float(m["ratio"])], True) + commands.setFloatProperty("#Session.matte.heightVisible", + [float(m["heightVisible"])], True) + commands.setFloatProperty("#Session.matte.centerPoint", + [float(m["centerX"]), float(m["centerY"])], True) + commands.setIntProperty("#Session.matte.show", [1], True) + extra_commands.displayFeedback( + "Using '%s' matte" % matte, 2.0) + commands.writeSettings("CUSTOM_MATTES", "customMatteName", matte) + return select +``` +Here notice the second to last line. We save the matte that was just selected. Lastly lets see what we have to do to make use of these when we initialize our mode. Here is the final version of the constructor: + +``` + class CustomMatteMinorMode(rvtypes.MinorMode): + + def __init__(self): + rvtypes.MinorMode.__init__(self) + self._order = [] + self._mattes = {} + self._currentMatte = "" + self.init("custom-mattes-mode", None, None, None) + + try: + definition = os.environ["RV_CUSTOM_MATTE_DEFINITIONS"] + except KeyError: + definition = str(commands.readSettings( + "CUSTOM_MATTES", "customMattesDefinition", "")) + try: + self.updateMattesFromFile(definition) + except KnownError,inst: + print(str(inst)) + self.setMenuAndBindings() + + lastMatte = str(commands.readSettings( + "CUSTOM_MATTES", "customMatteName", "")) + for matte in self._order: + if matte == lastMatte: + self.selectMatte(matte)(None) +``` +Here we grab the last known location of the mattes definition file if we did not find one in the environment. We also attempt to look up the last matte that was used and if we can find it among the mattes we parsed then we enable that selection. + +### 11.6 The Finished custom_mattes.py File + + +``` +from rv import commands, rvtypes, extra_commands +import os + +class KnownError(Exception): pass + +class CustomMatteMinorMode(rvtypes.MinorMode): + + def __init__(self): + rvtypes.MinorMode.__init__(self) + self._order = [] + self._mattes = {} + self._currentMatte = "" + self.init("custom-mattes-mode", None, None, None) + + try: + definition = os.environ["RV_CUSTOM_MATTE_DEFINITIONS"] + except KeyError: + definition = str(commands.readSettings( + "CUSTOM_MATTES", "customMattesDefinition", "")) + try: + self.updateMattesFromFile(definition) + except KnownError,inst: + print(str(inst)) + self.setMenuAndBindings() + + lastMatte = str(commands.readSettings( + "CUSTOM_MATTES", "customMatteName", "")) + for matte in self._order: + if matte == lastMatte: + self.selectMatte(matte)(None) + + def currentMatteState(self, m): + def matteState(): + if (m != "" and self._currentMatte == m): + return commands.CheckedMenuState + return commands.UncheckedMenuState + return matteState + + def selectMatte(self, matte): + + # Create a method that is specific to each matte for setting the + # relevant session node properties to display the matte + def select(event): + self._currentMatte = matte + if (matte == ""): + commands.setIntProperty("#Session.matte.show", [0], True) + extra_commands.displayFeedback("Disabling mattes", 2.0) + else: + m = self._mattes[matte] + commands.setFloatProperty("#Session.matte.aspect", + [float(m["ratio"])], True) + commands.setFloatProperty("#Session.matte.heightVisible", + [float(m["heightVisible"])], True) + commands.setFloatProperty("#Session.matte.centerPoint", + [float(m["centerX"]), float(m["centerY"])], True) + commands.setIntProperty("#Session.matte.show", [1], True) + extra_commands.displayFeedback( + "Using '%s' matte" % matte, 2.0) + commands.writeSettings("CUSTOM_MATTES", "customMatteName", matte) + return select + + def selectMattesFile(self, event): + definition = commands.openFileDialog(True, False, False, None, None)[0] + try: + self.updateMattesFromFile(definition) + except KnownError,inst: + print(str(inst)) + self.setMenuAndBindings() + + def setMenuAndBindings(self): + + # Walk through all of the mattes adding a menu entry as well as a + # hotkey binding for alt + index number + # NOTE: The bindings will only matter for the first 9 mattes since you + # can't really press "alt-10". + matteItems = [] + bindings = [] + if (len(self._order) > 0): + matteItems.append(("No Matte", self.selectMatte(""), "alt `", + self.currentMatteState(""))) + bindings.append(("key-down--alt--`", "")) + + for i,m in enumerate(self._order): + matteItems.append((m, self.selectMatte(m), + "alt %d" % (i+1), self.currentMatteState(m))) + bindings.append(("key-down--alt--%d" % (i+1), m)) + else: + def nada(): + return commands.DisabledMenuState + matteItems = [("RV_CUSTOM_MATTE_DEFINITIONS UNDEFINED", + None, None, nada)] + + # Always add the option to choose a new definition file + matteItems += [("_", None)] + matteItems += [("Choose Definition File...", self.selectMattesFile, + None, None)] + + # Clear the menu then add the new entries + matteMenu = [("View", [("_", None), ("Custom Mattes", None)])] + commands.defineModeMenu("custom-mattes-mode", matteMenu) + matteMenu = [("View", [("_", None), ("Custom Mattes", matteItems)])] + commands.defineModeMenu("custom-mattes-mode", matteMenu) + + # Create hotkeys for each matte + for b in bindings: + (event, matte) = b + commands.bind("custom-mattes-mode", "global", event, + self.selectMatte(matte), "") + + def updateMattesFromFile(self, filename): + + # Make sure the definition file exists + if (not os.path.exists(filename)): + raise KnownError("ERROR: Custom Mattes Mode: Non-existent mattes" + + " definition file: '%s'" % filename) + + # Clear existing key bindings + for i in range(len(self._order)): + commands.unbind( + "custom-mattes-mode", "global", "key-down--alt--%d" % (i+1)) + + # Walk through the lines of the definition file collecting matte + # parameters + order = [] + mattes = {} + for line in open(filename).readlines(): + tokens = line.strip("\n").split(",") + if (len(tokens) == 6): + order.append(tokens[0]) + mattes[tokens[0]] = { + "name" : tokens[0], "ratio" : tokens[1], + "heightVisible" : tokens[2], "centerX" : tokens[3], + "centerY" : tokens[4], "text" : tokens[5]} + + # Make sure we got some valid mattes + if (len(order) == 0): + self._order = [] + self._mattes = {} + raise KnownError("ERROR: Custom Mattes Mode: Empty mattes" + + " definition file: '%s'" % filename) + + # Save the definition path and assign the mattes + commands.writeSettings( + "CUSTOM_MATTES", "customMattesDefinition", filename) + self._order = order + self._mattes = mattes + +def createMode(): + return CustomMatteMinorMode() +``` \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-fifteen.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-fifteen.md new file mode 100644 index 000000000..b41078615 --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-fifteen.md @@ -0,0 +1,18 @@ +# Chapter 15 - Hierarchical Preferences + +Each RV user has a Preferences file where her personal rv settings are stored. Most preferences are viewed and edited with the Preferences dialog (accessed via the RV menu), but preferences can also be programmatically read and written from custom code via the readSetting and writeSetting Mu commands. The preferences files are stored in different places on different platforms. + +| Platform | Location | +| --- | --- | +| macOS | $HOME/Library/Preferences/com.autodesk.OpenRV.plist | +| Linux | $HOME/.config/Autodesk/OpenRV.conf | +| Windows 7 | ~$HOME/AppData/Roaming/Autodesk/OpenRV.ini | + +Initial values of preferences can be overridden on a site-wide or show-wide basis by setting the environment variable RV_PREFS_OVERRIDE_PATH to point to one or more directories that contain files of the name and type listed in the above table. For example, if you have your RV.conf file under $HOME/Documents/ you can set RV_PREFS_OVERRIDE_PATH=$HOME/Documents. Each of these overriding preferences file can provide default values for one or more preferences. A value from one of these overriding files will override the users's preference only if the user's preferences file has no value for this preference yet.In the simplest case, if you want to provide overriding initial values for all preferences, you should + +1. Delete your preferences file. +2. Start RV, go to the Preferences dialog, and adjust any preferences you want. +3. Close the dialog and exit RV. +4. Copy your preferences file into the RV_PREFS_OVERRIDE_PATH. + +If you want to override at several levels (say per-site and per-show), you can add preferences files to any number of directories in the PATH, but you'll have to edit them so that each only contains the preferences you want to override with that file. Preferences files found in directories earlier in the path will override those found in later directories.Note that this system only provides the ability to override initial settings for the preferences. Nothing prevents the user from changing those settings after initialization.It's also possible to create show/site/whatever-specific preferences files that **always** clobber the user's personal preferences. This mechanism is exactly analogous to the above, except that the name of the environment variable that holds paths to clobbering prefs files is RV_PREFS_CLOBBER_PATH. Again, the user can freely change any “live” values managed in the Preferences dialog, but in the next run, the clobbering preferences will again take precedence. Note that a value from a clobbering file (at any level) will take precedence over a value from an overriding file (at any level). \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-five.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-five.md new file mode 100644 index 000000000..73ed2f1f6 --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-five.md @@ -0,0 +1,302 @@ +# Chapter 5 - Event Handling + +Aside from rendering, the most important function of the UI is to handle events. An event can be triggered by any of the following: + +* The mouse pointer moved or a button on the mouse was pressed +* A key on the keyboard was pressed or released +* The window needs to be re-rendered +* A file being watched was changed +* The user became active or inactive +* A supported device (like the apple remote control) did something +* An internal event like a new source or session being created has occurred + +Each specific event has a name may also have extra data associated with it in the form of an event object. To see the name of an event (at least for keyboard and mouse pointer events) you can select the Help → Describe... which will let you interactively see the event name as you hit keys or move the mouse. You can also use Help → Describe Key.. to see what a specific key is bound to by pressing it.Table [5.1](#event-prefixes-for-basic-device-events) shows the basic event type prefixes. + +| Event Prefix | Description | +| --- | --- | +| key-down | Key is being pressed on the keyboard | +| key-up | Key is being released on the keyboard | +| pointer | The mouse moved, button was pressed, or the pointer entered (or left) the window | +| dragdrop | Something was dragged onto the window (file icon, etc) | +| render | The window needs updating | +| user | The user's state changed (active or inactive, etc) | +| remote | A network event | + +Table 5.1: Event Prefixes for Basic Device Events + +When an event is generated in RV, the application will look for a matching event name in its bindings. The bindings are tables of functions which are assigned to certain event names. The tables form a stack which can be pushed and popped. Once a matching binding is found, RV will execute the function.When receiving an event, all of the relevant information is in the Event object. This object has a number of methods which return information depending on the kind of event. + +| Method | Events | Description | +| --- | --- | --- | +| pointer (Vec2;) | pointer-\* dragdrop-\* | Returns the location of the pointer relative to the view. | +| relativePointer (Vec2;) | pointer-\* dragdrop-\* | Returns the location of the pointer relative to the current widget or view if there is none. | +| reference (Vec2;) | pointer-\* dragdrop-\* | Returns the location of initial button mouse down during dragging. | +| domain (Vec2;) | pointer-\* render-\* dragdrop-\* | Returns the size of the view. | +| subDomain (Vec2;) | pointer-\* render-\* dragdrop-\* | Returns the size of the current widget if there is one. relativePointer() is positioned in the subDomain(). | +| buttons (int;) | pointer-\* dragdrop-\* | Returns an int or'd from the symbols: Button1, Button2, and Button3. | +| modifiers (int;) | pointer-\* key-\* dragdrop-\* | Returns an int or'd from the symbols: None, Shift, Control, Alt, Meta, Super, CapLock, NumLock, ScrollLock. | +| key (int;) | key-\* | Returns the “keysym” value for the key as an int | +| name (string;) | any | Returns the name of the event | +| contents (string;) | internal eventsdragdrop-\* | Returns the string content of the event if it has any. This is normally the case with internal events like new-source, new-session, etc. Pointer, key, and other device events do not have a contents() and will throw if it's called on them. Drag and drop events return the data associated with them. Some render events have contents() indicating the type of render occurring. | +| contentsArray (string[];) | internal events | Same as contents(), but in the case of some internal events ancillary information may be present which can be used to avoid calling additional commands. | +| sender (string;) | any | Returns the name of the sender | +| contentType (int;) | dragdrop-\* | Returns an int describing the contents() of a drag and drop event. One of: UnknownObject, BadObject, FileObject, URLObject, TextObject. | +| timeStamp (float;) | any | Returns a float value in seconds indicating when the event occurred | +| reject (void;) | any | Calling this function will cause the event to be send to the next binding found in the event table stack. Not calling this function stops the propagation of the event. | +| setReturnContents (void; string) | internal events | Events which have a contents may also have return content. This is used by the remote network events which can have a response. | + +Table 5.2:Event Object Methods. Python methods have the same names and return the same value types. + +### 5.1 Binding an Event + + +In Mu (or Python) you can bind an event using any of the bind() functions. The most basic version of bind() takes the name of the event and a function to call when the event occurs as arguments. The function argument (which is called when the event occurs) should take an Event object as an argument and return nothing (void). Here's a function that prints hello in the console every time the \`\`j'' key is pressed: + +> **Note:** If this is the first time you've seen this syntax, it's defining a Mu function. The first two characters \\: indicate a function definition follows. The name comes next. The arguments and return type are contained in the parenthesis. The first identifier is the return type followed by a semicolon, followed by an argument list. + +E.g, \: add (int; int a, int b) { return a + b; } + +``` +\: my_event_function (void; Event event) +{ + print("Hello!\n"); +} + +bind("key-down--j", my_event_function); +``` + +or in Python: + +``` + def my_event_function (event): + print ("Hello!") + +bind("default", "global", "key-down--j", my_event_function); +``` +There are more complicated bind() functions to address binding functions in specific event tables (the Python example above is using the most general of these). Currently RV's user interface has one default global event table an couple of other tables which implement the parameter edit mode and help modes.Many events provide additional information in the event object. Our example above doesn't even use the event object, but we can change it to print out the key that was pressed by changing the function like so: + +``` + \: my_event_function (void; Event event) +{ + let c = char(event.key()); + print("Key pressed = %c\n" % c); +} +``` +or in Python: + +``` + def my_event_function (event): + c = event.key() + print ("Key pressed = %s\n" % c) +``` +In this case, the Event object's key() function is being called to retrieve the key pressed. To use the return value as a key it must be cast to a char. In Mu, the char type holds a single unicode character. In Python, a string is unicode. See the section on the Event class to find out how to retrieve information from it. At this point we have not talked about *where* you would bind an event; that will be addressed in the customization sections. + +### 5.2 Keyboard Events + + +There are two keyboard events: key-down and key-up. Normally the key-down events are bound to functions. The key-up events are necessary only in special cases.The specific form for key down events is key-down– *something* where *something* uniquely identifies both the key pressed and any modifiers that were active at the time.So if the \`\`a'' key was pressed the event would be called: key-down–a. If the control key were held down while hitting the \`\`a'' key the event would be called key-down–control–a.There are five modifiers that may appear in the event name: alt, caplock, control, meta, numlock, scrolllock, and shift in that order. The shift modifier is a bit different than the others. If a key is pressed with the shift modifier down and it would result in a different character being generated, then the shift modifier will not appear in the event and instead the result key will. This may sound complicated but these examples should explain it:For control + shift + A the event name would be key-down–control–A. For the \`\`\*'' key (shift + 8 on American keyboards) the event would be key-down–\*. Notice that the shift modifier does not appear in any of these. However, if you hold down shift and hit enter on most keyboards you will get key-down–shift–enter since there is no character associated with that key sequence.Some keys may have a special name (like enter above). These will typically be spelled out. For example pressing the \`\`home'' key on most keyboards will result in the event key-down–home. The only way to make sure you have the correct event name for keys is to start RV and use the Help → Describe... facility to see the true name. Sometimes keyboards will label a key and produce an unexpected event. There will be some keyboards which will not produce an event all for some keys or will produce a unicode character sequence (which you can see via the help mechanism). + +### 5.3 Pointer (Mouse) Events + + +The mouse (called pointer from here on) can produce events when it is moved, one of its buttons is pressed, an attached scroll wheel is rotated, or the pointer enters or leaves the window.The basic pointer events are move, enter, leave, wheelup, wheeldown, push, drag, and release. All but enter and leave will also indicate any keyboard modifiers that are being pressed along with any buttons on the mouse that are being held down. The buttons are numbered 1 through 5. For example if you hold down the left mouse button and movie the mouse the events generated are: + +``` +pointer-1--push +pointer-1--drag +pointer-1--drag +... +pointer-1-release +``` +Pointer events involving buttons and modifiers always come in there parts: push, drag and release. So for example if you press the left mouse, move the mouse, press the shift key, move the mouse, release everything you get: + +``` +pointer-1--push +pointer-1--drag +pointer-1--drag +... +pointer-1-release +pointer-1--shift--push +pointer-1--shift--drag +pointer-1--shift--drag +... +pointer-1--shift--release +``` +Notice how the first group without the shift is released before starting the second group with the shift even though you never released the mouse button. For any combination of buttons and modifiers, there will be a push-drag-release sequence that is cleanly terminated.It is also possible to hold multiple mouse buttons and modifiers down at the same time. When multiple buttons are held (for example, button 1 and 2) they are simply both included (like the modifiers) so for buttons 1 and 2 the name would be pointer-1-2–push to start the sequence.The mouse wheel behaves more like a button: when the wheel moves you get only a wheelup or wheeldown event indicating which direction the wheel was rotated. The buttons and modifiers will be applied to the event name if they are held down. Usually the motion of the wheel on a mouse will not be smooth and the event will be emitted whenever the wheel \`\`clicks''. However, this is completely a function of the hardware so you may need to experiment with any particular mouse.There are three more pointer events that can be generated. When the mouse moves with no modifiers or buttons held down it will generate the event pointer–move. When the pointer enters the view pointer–enter is generated and when it leaves pointer–leave. Something to keep in mind: when the pointer leaves the view and the device is no longer in focus on the RV window, any modifiers or buttons the user presses will not be known to RV and will not generate events. When the pointer returns to the view it may have modifiers that became active when out-of-focus. Since RV cannot know about these modifiers and track them in a consistent manner (at least on X Windows) RV will assume they do not exist.Pointer events have additional information associated with them like the coordinates of the pointer or where a push was made. These will be discussed later. + +### 5.4 The Render Event + + +The UI will get a render event whenever it needs to be updated. When handling the render event, a GL context is set up and you can call any GL function to draw to the screen. The event supplies additional information about the view so you can set up a projection.At the time the render event occurs, RV has already rendered whatever images need to be displayed. The UI is then called in order to add additional visual objects like an on-screen widget or annotation.Here's a render function that draws a red polygon in the middle of the view right on top of your image.Listing 5.1:Example Render Function + +``` + \: my_render (void; Event event) +{ + let domain = event.domain(), + w = domain.x, + h = domain.y, + margin = 100; + + use gl; + use glu; + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + gluOrtho2D(0.0, w, 0, h); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + // Big red polygon + glColor(Color(1,0,0,1)); + glBegin(GL_POLYGON); + glVertex(margin, margin); + glVertex(w-margin, margin); + glVertex(w-margin, h-margin); + glVertex(margin, h-margin); + glEnd(); +} +``` +Note that for Python, you will need to use the PyOpenGL module or bind the symbols in the gl Mu module manually in order to draw in the render event.The UI code already has a function called render() bound the render event; so this function basically turns off existing UI rendering. + +### 5.5 Remote Networking Events + + +RV's networking generates a number of events indicating the status of the network. In addition, once a connection has been established, the UI may generate sent to remote programs, or remote programs may send events to RV. These are typically uniquely named events which are specific to the application that is generating and receiving them.For example the sync mechanism generates a number of events which are all named remote-sync-something. + +### 5.6 Internal Events + + +Some events will originate from RV itself. These include things like new-source or new-session which include information about what changed. The most useful of these is new-source which can be used to manage color and other image settings between the time a file is loaded and the time it is first displayed. (See Color Management Section). Other internal events are functional, but are placeholders which will become useful with future features.The current internal events are listed in table [5.3](#internal-events) . + +| Event | Event.(data/contents) | Ancillary Data (contentsArray) | Description | +| --- | --- | --- | --- | +| render | | | Main view render | +| pre-render | | | Before rendering | +| post-render | | | After rendering | +| per-render-event-processing | | | Qt Event processing between renders (a “safe” time to edit the graph) | +| layout | | | Main view layout used to handle view margin changes | +| new-source | nodename;;RVSource;;filename | | **DEPRECATED** A new source node was added (or media was reset) | +| source-group-complete | group nodename;;action_type | | A new or modified source group is complete | +| source-modified | nodename;;RVSource;;filename | | An existing source was changed | +| media-relocated | nodename;;oldmedia;;newmedia | | A movie, image sequence, audio file was swapped out | +| source-media-set | nodename;;tag | | | +| before-session-read | filename | | Session file is about to be read | +| after-session-read | filename | | Session file was read | +| before-session-write | filename | | Session file is about to be written | +| after-session-write | filename | | Session file was just written | +| before-session-write-copy | filename | | A copy of the session is about to be written | +| after-session-write-copy | filename | | A copy of a session was just written | +| before-session-deletion | | | The session is about to be deleted | +| before-graph-view-change | nodename | | The current view node is about to change. | +| after-graph-view-change | nodename | | The current view node changed. | +| new-node | nodename | | A new view node was created. | +| graph-new-node | nodename | nodename protocol version groupname | A new node of any kind was created. | +| before-progressive-loading | | | Loading will start | +| after-progressive-loading | | | Loading is complete (sent immediately if no files will be loaded) | +| graph-layer-change | | | **DEPRECATED** use after-graph-view-change | +| frame-changed | | | The current frame changed | +| fps-changed | | | Playback FPS changed | +| play-start | | | Playback started | +| play-stop | | | Playback stopped | +| incoming-source-path | infilename;;tag | | A file was selected by the user for loading. | +| missing-image | | | An image could not be loaded for rendering | +| cache-mode-changed | buffer or region or off | | Caching mode changed | +| view-size-changed | | | The viewing area size changed | +| new-in-point | frame | | The in point changed | +| new-out-point | frame | | The out point changed | +| before-source-delete | nodename | | Source node will be deleted | +| after-source-delete | nodename | | Source node was deleted | +| before-node-delete | nodename | | View node will be deleted | +| after-node-delete | nodename | | View node was deleted | +| after-clear-session | | | The session was just cleared | +| after-preferences-write | | | Preferences file was written by the Preferences GUI | +| state-initialized | | | Mu/Python init files read | +| session-initialized | | | All modes toggled, command-line processed, etc. | +| realtime-play-mode | | | Playback mode changed to realtime | +| play-all-frames-mode | | | Playback mode changed to play-all-frames | +| before-play-start | | | Play mode will start | +| mark-frame | frame | | Frame was marked | +| unmark-frame | frame | | Frame was unmarked | +| pixel-block | Event.data() | | A block of pixels was received from a remote connection | +| graph-state-change | | | A property in the image processing graph changed | +| graph-node-inputs-changed | nodename | | Inputs of a top-level node added/removed/re-ordered | +| range-changed | | | The time range changed | +| narrowed-range-changed | | | The narrowed time range changed | +| margins-changed | left right top bottom | | View margins changed | +| view-resized | old-w new-w | old-h new-h | | Main view changed size | +| preferences-show | | | Pref dialog will be shown | +| preferences-hide | | | Pref dialog was hidden | +| read-cdl-complete | cdl_filename;;cdl_nodename | | CDL file has been loaded | +| read-lut-complete | lut_filename;;lut_nodename | | LUT file has been loaded | +| remote-eval | code | | Request to evaluate external Mu code | +| remote-pyeval | code | | Request to evaluate external Python code | +| remote-pyexec | code | | Request to execute external Python code | +| remote-network-start | | | Remote networking started | +| remote-network-stop | | | Remote networking stopped | +| remote-connection-start | contact-name | | A new remote connection has been made | +| remote-connection-stop | contact-name | | A remote connection has died | +| remote-contact-error | contact-name | | A remote connection error occurred while being established | + +Table 5.3:Internal Events + +#### 5.6.1 File Changed Event + +It is possible to watch a file from the UI. If the watched file changes in any way (modified, deleted, moved, etc) a file-changed event will be generated. The event object will contain the name of the watched file that changed. A function bound to file-changed might look something like this: + +``` + \: my_file_changed (void; Event event) +{ + let file = event.contents(); + print("%s changed on disk\n" % file); +} +``` +In order to have a file-changed event generated, you must first have called the command function watchFile(). + +#### 5.6.2 Incoming Source Path Event + +This event is sent when the user has selected a file or sequence to load from the UI or command line. The event contains the name of the file or sequence. A function bound to this event can change the file or sequence that RV actually loads by setting the return contents of the event. For example, you can cause RV to check and see if a single file is part of a larger sequence and if so load the whole sequence like so: + +``` + \: load_whole_sequence (void; Event event) +{ + let file = event.contents(), + (seq,frame) = sequenceOfFile(event.contents()); + + if (seq != "") event.setReturnContent(seq); +} + +bind("incoming-source-path", load_whole_sequence); +``` +or in Python: + +``` + def load_whole_sequence (event): + + file = event.contents(); + (seq,frame) = rv.commands.sequenceOfFile(event.contents()); + + if seq != "": + event.setReturnContent(seq); + + +bind("default", "global", "incoming-source-path", load_whole_sequence, "Doc string"); +``` + +#### 5.6.3 Missing Images + +Sometimes an image is not available on disk when RV tries to read. This is often the case when looking at an image sequence while a render or composite is ongoing. By default, RV will find a nearby frame to represent the missing frame if possible. The missing-image event will be sent once for each image which was expected but not found. The function bound to this event can render information on on the screen indicating that the original image was missing. The default binding display a message in the feedback area.The missing-image event contains the domain in which rendering can occur (the window width and height) as well as a string of the form \`\`frame;source'' which can be obtained by calling the contents() function on the event object.The default binding looks like this: + +``` + \: missingImage (void; Event event) +{ + let contents = event.contents(), + parts = contents.split(";"), + media = io.path.basename(sourceMedia(parts[1])._0); + + displayFeedback("MISSING: frame %s of %s" + % (parts[0], media), 1, drawXGlyph); +} + +bind("missing-image", missingImage); +``` \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-four.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-four.md new file mode 100644 index 000000000..bfcd83aac --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-four.md @@ -0,0 +1,273 @@ +# Chapter 4 - Python + +You can use Python3 in RV in conjunction with Mu or in place of it. It's even possible to call Python commands from Mu and vice versa. Python is a full peer to Mu as far as RV is concerned. So in answer to the question: which language should I use to customize RV? The answer is whichever you like. At this point we recommend using Python. There are some slight differences that need to be noted when translating code between the two languages: In Python the modules names required by RV are the same as in Mu. As of this writing, these are commands, extra_commands, rvtypes, and rvui. However, the Python modules all live in the rv package. This package is in-memory and only available at RV's runtime. You can access these commands via writing your own custom MinorMode package. So while in Mu, you can: + +``` + use commands +``` + +``` + require commands +``` + +to make the commands visible in the current namespace. In Python you need to include the package name: + +```python + from rv.commands import * +``` + +or + +```python + import rv.commands +``` + +### Open RV Python quickstart + +In order to extend RV using Python you will be making a "mode" as part of an rvpkg package—this is identical to the way it’s done in Mu and this is the method that we use internally to add new functions to RV's interface. Creation of a modes and packages is documented later in this chapter. Here is a very simple mode written in Python which is part of the RV packages as `pyhello-1.1.rvpkg`. + +```python +import rv.rvtypes +import rv.commands + +class PyHello(rv.rvtypes.MinorMode): + "A simple example that shows how to make shift-Z start/stop playback" + + def togglePlayback(self, event): + if rv.commands.isPlaying(): + rv.commands.stop() + else: + rv.commands.play() + + def __init__(self): + rv.rvtypes.MinorMode.__init__(self) + self.init("pyhello", [("key-down--Z", self.togglePlayback, "Z key")], None) + + def createMode(): + "Required to initialize the module. RV will call this function to create your mode." + return PyHello() +``` + +#### Documentation + +The command API is nearly identical to Mu. There are a few modules which are important to know about: `rv.rvtypes`, `rv.commands`, `rv.extra_commands`, `and rv.rvui`. These implement the base Python interface to RV. + +There is no separate documentation for RV's command API in Python (e.g., via Pydoc), but you can use the existing Mu Command API Browser available under RV's Help menu. The commands and extra_commands modules are basically identical between the two languages. + +### 4.1 Calling Mu From Python + +It's possible to call Mu code from Python, but in practice you will probably not need to do this unless you need to interface with existing packages written in Mu. To call a Mu function from Python, you need to import the MuSymbol type from the pymu module. In this example, the play function is imported and called F on the Python side. F is then executed: + +```python +from pymu import MuSymbol +F = MuSymbol("commands.play") +F() +``` + +If the Mu function has arguments you supply them when calling. Return values are automatically converted between languages. The conversions are indicated in Figure [4.3](#43-python-mu-type-conversions). + +```python +from pymu import MuSymbol +F = MuSymbol("commands.isPlaying") +G = MuSymbol("commands.setWindowTitle") +if F() == True: + G("PLAYING") +``` + +Once a MuSymbol object has been created, the overhead to call it is minimal. All of the Mu commands module is imported on start up or reimplemented as native CPython in the Python rv.commands module so you will not need to create MuSymbol objects yourself; just import rv.commands and use the pre-existing ones. + +When a Mu function parameter takes a class instance, a Python dictionary can be passed in. When a Mu function returns a class, a dictionary will be returned. Python dictionaries should have string keys which have the same names as the Mu class fields and corresponding values of the correct types. For example, the Mu class Foo { int a; float b; } as instantiated as Foo(1, 2.0) will be converted to the Python dictionary {'a' : 1, 'b' : 2.0} and vice versa. Existing Mu code can be leveraged with the rv.runtime.eval call to evaluate arbitrary Mu from Python. The second argument to the eval function is a list of Mu modules required for the code to execute and the result of the evaluation will be returned as a string. For example, here's a function that could be a render method on a mode; it uses the Mu gltext module to draw the name of each visible source on the image: + +```python + def myRender (event) : + event.reject() + + for s in rv.commands.renderedImages() : + if (rv.commands.nodeType(rv.commands.nodeGroup(s["node"])) != "RVSourceGroup") : + continue + geom = rv.commands.imageGeometry(s["name"]) + + if (len(geom) == 0) : + continue + + x = geom[0][0] + y = (geom[0][1] + geom[2][1]) / 2.0 + domain = event.domain() + w = domain[0] + h = domain[1] + + drawCode = """ + { + rvui.setupProjection (%d, %d); + gltext.color (rvtypes.Color(1.0,1.0,1.0,1)); + gltext.size(14); + gltext.writeAt(%f, %f, extra_commands.uiName("%s")); + } + """ + rv.runtime.eval(drawCode % (w, h, float(x), float(y), s["node"]), ["rvui", "rvtypes", "extra_commands"]) +``` + +> **Note:** Python code in RV can assume that default parameters in Mu functions will be supplied if needed. + +### 4.2 Calling Python From Mu + +There are two ways to call Python from Mu code: a Python function being used as a call back function from Mu or via the "python" Mu module. In order to use a Python callable object as a call back from Mu code simply pass the callable object to the Mu function. The call back function's arguments will be converted according to the Mu to Python value conversion rules show in Figure [4.3](#43-python-mu-type-conversions) . There are restrictions on which callable objects can be used; only callable objects which return values of None, Float, Int, String, Unicode, Bool, or have no return value are currently allowed. Callable objects which return unsupported values will cause a Mu exception to be thrown after the callable returns. The Mu "python" module implements a small subset of the CPython API. You can see documentation for this module in the Mu Command API Browser under the Help menu. Here is an example of how you would call os.path.join from Python in Mu. + +``` +require python; + +let pyModule = python.PyImport_Import ("os"); + +python.PyObject pyMethod = python.PyObject_GetAttr (pyModule, "path"); +python.PyObject pyMethod2 = python.PyObject_GetAttr (pyMethod, "join"); + +string result = to_string(python.PyObject_CallObject (pyMethod2, ("root","directory","subdirectory","file"))); + +print("result: %s\n" % result); // Prints "result: root/directory/subdirectory/file" +``` + +If the method you want to call takes no arguments like os.getcwd, then you will want to call it in the following manner. + +``` +require python; + +let pyModule = python.PyImport_Import ("os"); + +python.PyObject pyMethod = python.PyObject_GetAttr (pyModule, "getcwd"); + +string result = to_string(python.PyObject_CallObject (pyMethod, PyTuple_New(0))); + +print("result: %s\n" % result); // Prints "result: /var/tmp" +``` + +If the method you want to call require the python class instance "self" as an argument, you can get it by using the ModeManager as in the following exemple + +``` +let pyModule = python.PyImport_Import ("sgtk_bootstrap"); +python.PyObject pyMethod = python.PyObject_GetAttr (pyModule, "ToolkitBootstrap"); +python.PyObject pyMethod2 = python.PyObject_GetAttr (pyMethod, "queue_launch_import_cut_app"); + +State state = data(); +ModeManagerMode manager = state.modeManager; +ModeManagerMode.ModeEntry entry = manager.findModeEntry ("sgtk_bootstrap"); + +if (entry neq nil) +{ + PyMinorMode sgtkMode = entry.mode; + python.PyObject_CallObject (pyMethod2, (sgtkMode._pymode, "no event")); +} +``` + +If you are interested in retrieving an attribute alone then here is an example of how you would call sys.platform from Python in Mu. + +``` +require python; + +let pyModule = python.PyImport_Import ("sys"); + +python.PyObject pyAttr = python.PyObject_GetAttr (pyModule, "platform"); + +string result = to_string(pyAttr); + +print("result: %s\n" % result); // Prints "result: darwin" +``` + +### 4.3 Python Mu Type Conversions + +| Python Type | Converts to Mu Type | Converts To Python Type | | +| --- | --- | --- | --- | +| Str or Unicode | string | Unicode string | Normal byte strings and unicode strings are both converted to Mu's unicode string. Mu strings always convert to unicode Python strings. | +| Int | int, short, or byte | Int | | +| Long | int64 | Long | | +| Float | float or half or double | Float | Mu double values may lose precision. Python float values may lose precision if passed to a Mu function that takes a half. | +| Bool | bool | Bool | | +| (Float, Float) | vector float[2] | (Float, Float) | Vectors are represented as tuples in Python | +| (Float, Float, Float) | vector float[3] | (Float, Float, Float) | | +| (Float, Float, Float, Float) | vector float[4] | (Float, Float, Float, Float) | | +| Event | Event | Event | | +| MuSymbol | runtime.symbol | MuSymbol | | +| Tuple | tuple | Tuple | Tuple elements each convert independently. NOTE: two to four element Float tuples will convert to vector float[N] in Mu. Currently there is no way to force conversion of these Float-only tuples to Mu float tuples. | +| List | type[] or type[N] | List | Arrays (Lists) convert back and forth | +| Dictionary | Class | Dictionary | Class labels become dictionary keys | +| Callable Object | Function Object | Not Applicable | Callable objects may be passed to Mu functions where a Mu function type is expected. This allows Python functions to be used as Mu call back functions. | + +Table 4.1:Mu-Python Value Conversion + +### 4.4 PySide Example + +You can use PySide2 to make Qt interface components (RV is a Qt Application). Below is a simple pyside example using RV's py-interp. + +```python +#!/Applications/RV64.app/Contents/MacOS/py-interp + +# Import PySide classes +import sys +from PySide.QtCore import * +from PySide.QtGui import * + +# Create a Qt application. +# IMPORTANT: RV's py-interp contains an instance of QApplication; +# so always check if an instance already exists. +app = QApplication.instance() +if app == None: + app = QApplication(sys.argv) + +# Display the file path of the app. +print app.applicationFilePath() + +# Create a Label and show it. +label = QLabel("Using RV's PySide") +label.show() + +# Enter Qt application main loop. +app.exec_() + +sys.exit() +``` +To access RV's essential session window Qt QWidgets, i.e. the main window, the GL view, top tool bar and bottom tool bar, import the Python module 'rv.qtutils'. + +```python +import rv.qtutils + +# Gets the current RV session windows as a PySide QMainWindow. +rvSessionWindow = rv.qtutils.sessionWindow() + +# Gets the current RV session GL view as a PySide QGLWidget. +rvSessionGLView = rv.qtutils.sessionGLView() + +# Gets the current RV session top tool bar as a PySide QToolBar. +rvSessionTopToolBar = rv.qtutils.sessionTopToolBar() + +# Gets the current RV session bottom tool bar as a PySide QToolBar. +rvSessionBottomToolBar = rv.qtutils.sessionBottomToolBar() +``` + +### 4.5 Open RV Python Implementation FAQ + +#### Can I draw on the view the way Mu does using OpenGL? + +Yes. If you bind to a render event you can draw using PyOpenGL. + +#### Module XXX is missing. Where can I get it? + +Use `py-interp -m pip` to get the missing package, like you would any other python package. + +If the module is not included and it’s a CPython module (written in C) you will need to compile it yourself. The compiled module must be added to the `Python` plug-ins folder. + +#### The commands.bind() function in Python doesn’t work the same way as in Mu? How do I use it? + +Python currently requires all arguments to bind(). So to make it the "short form" do: + +```python +bind("default", "global", event, func, event_doc_string). +``` + +#### Does the Python <-> Mu bridge slow things down? + +The Python <-> Mu bridge does not slow things down. The MuSymbol type used to interface between them completely skips interpreted Mu code if it’s calling a "native" Mu function from Python. All of the RV commands are native Mu functions. So there’s a thin layer between the Python call and the actual underlying RV command (which is largely language agnostic). + +The Mu calling into Python bridge is roughly the cost of calling a Python function from C. + +## Why does my external Python process (which I call from Open RV) now behave differently? + +This is probably caused by the fact that RV modifies the PYTHONPATH to incorporate the Python plug-in folder and RV's python standard libraries to run. Forked processes will inherit the PYTHONPATH. If you are using QProcess to launch the external process you can call `QProcess.setEnvironment()` to set the PYTHONPATH before calling `QProcess.start()`. diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-fourteen.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-fourteen.md new file mode 100644 index 000000000..37548bc36 --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-fourteen.md @@ -0,0 +1,145 @@ +# Chapter 14 - Webkit JavaScript Integration + +RV can communicate with JavaScript running in a Qt WebEngine widget. This makes it possible to serve custom RV-aware web pages which can interact with a running RV. JavaScript running in the web page can execute arbitrary Mu script strings as well as receive events from RV.You can experiment with this using the example webview package included with RV. + +### 14.1 Executing Mu or Python from JavaScript + +RV exports a JavaScript object called rvsession to the Javascript runtime environment. Two of the functions in that namespace are evaluate() and pyevaluate(). By calling evaluate() or pyevaluate() or pyexec() you can execute arbitrary Mu or Python code in the running RV to control it. If the executed code returns a value, the value will be converted to a string and returned by the (py)evaluate() functions. Note that pyevaluate() triggers a python eval which takes an expression and returns a value. pyexec() on the other hand takes an arbitrary block of code and triggers a python exec call.As an example, here is some html which demonstates creating a link in a web page which causes RV to start playing when pressed: + +``` + + +

Play

+``` + +If inlining the Mu or Python code in each call back becomes onerous you can upload function definitions and even whole classes all in one evaluate call and then call the defined functions later. For complex applications this may be the most sane way to handle call back evaluation. + +### 14.2 Getting Event Call Backs in JavaScript + + +RV generates events which can be converted into call backs in JavaScript. This differs slightly from how events are handled in Mu and Python. + +| Signal | Events | +| --- | --- | +| eventString | Any internal RV event and events generated by the command sendInternalEvent() command in Mu or Python | +| eventKey | Any key- event (e.g. key-down–a) | +| eventPointer | Any pointer- event (e.g. pointer-1–push) or tablet event (e.g. stylus-pen–push) | +| eventDragDrop | Any dragdrop- event | + +Table 14.1:JavaScript Signals Produced by Events + +The rvsession object contains signal objects which you can connect by supplying a call back function. In addition you need to supply the name of one or more events as a regular expression which will be matched against incoming events. For example: + +``` +function callback_string (name, contents, sender) +{ + var x = name + " " + contents + " " + sender; + rvsession.evaluate("print(\"callback_string " + x + "\\n\");"); +} + +rvsession.eventString.connect(callback_string); +rvsession.bindToRegex("source-group-complete"); +``` +connects the function callback_string() to the eventString signal object and binds to the source-group-complete RV event. For each event the proper signal object type must be used. For example pointer events are not handled by eventString but by the eventPointer signal. There are four signals available: eventString, eventKey, eventPointer, and eventDragDrop. See tables describing which events generate which signals and what the signal call back arguments should be.In the above example, any time media is loaded into RV the callback_string() function will be called. Note that there is a single callback for each type of event. In particular if you want to handle both the “new-source” and the “frame-changed” events, your eventString handler must handle both (it can distinguish between them using the “name” parameter passed to the handler. To bind the handler to both events you can call “bindToRegex” multiple times, or specify both events in a regular expression: + +``` + rvsession.bindToRegex("source-group-complete|frame-changed"); +``` + +The format of this regular expression is specified [on the qt-project website](http://qt-project.org/doc/qt-4.8/qregexp.html) . + +| Argument | Description | +| --- | --- | +| eventName | The name of the RV event. For example “ **source-group-complete** ” | +| contents | A string containing the event contents if it has any | +| senderName | Name of the sender if it has one | + +Table 14.2:eventString Signal Arguments + +| Argument | Description | +| --- | --- | +| eventName | The name of the RV event. For example “ **source-group-complete** ” | +| key | An integer representing the key symbol | +| modifiers | An integer the low order five bits of which indicate the keyboard modifier state | + +Table 14.3:eventKey Signal Arguments + +| Argument | Description | +| --- | --- | +| eventName | The name of the RV event. For example “ **source-group-complete** ” | +| x | The horizontal position of the mouse as an integer | +| y | The vertical position of the mouse as an integer | +| w | The width of the event domain as an integer | +| h | The height of the event domain as an integer | +| startX | The starting horizontal position of a mouse down event | +| startY | The starting vertical position of a mouse down event | +| buttonStates | An integer the lower order five bits of which indicate the mouse button states | +| activationTime | The relative time at which button activation occurred or 0 for regular pointer events | + +Table 14.4:eventPointer Signal Arguments + +| Argument | Description | +| --- | --- | +| eventName | The name of the RV event. For example “ **source-group-complete** ” | +| x | The horizontal position of the mouse as an integer | +| y | The vertical position of the mouse as an integer | +| w | The width of the event domain as an integer | +| h | The height of the event domain as an integer | +| startX | The starting horizontal position of a mouse down event | +| startY | The starting vertical position of a mouse down event | +| buttonStates | An integer the lower order five bits of which indicate the mouse button states | +| dragDropType | A string the value of which will be one of “enter”, “leave”, “move”, or “release” | +| contentType | A string the value of which will be one of “file”, “url”, or “text” | +| stringContent | The contents of the drag and drop event as a string | + +Table 14.5:eventDragDrop Signal Arguments + +### 14.3 Using the webview Example Package + + +This package creates one or more docked Qt WebEngine instances, configurable from the command line as described below. JavaScript code running in the webviews can execute arbitrary Mu code in RV by calling the rvsession.evaluate() function. This package is intended as an example.These command-line options should be passed to RV after the -flags option. The webview options below are shown with their default values, and all of them can apply to any of four webviews in the Left, Right, Top, and Bottom dock locations. + +``` + shell> rv -flags ModeManagerPreload=webview +``` +The above forces the load of the webview package which will display an example web page. Additional arguments can be supplied to load specific web pages into additional panes. While this will just show the sample html/javascript file that comes with the package in a webview docked on the right. To see what's happening in this example, bring up the Session Manager so you can see the Sources appearing and disappearing, or switch to the defaultLayout view. Note that you can play while reconfiguring the session with the javascript checkboxes.The following additional arguments can be passed via the -flags mechanism. In the below, **POS** should be replaced by one of Left, Right, Bottom, or Top. + +ModeManagerPreload=webview + +Force loading of the webview package. The package should not be loaded by default but does need to be installed. This causes rv to treat the package as if it were loaded by the user. + +webviewUrlPOS=URL + +A webview pane will be created at POS and the URL will be loaded into it. It can be something from a web server or a file:// URL. If you force the package to load, but do not specify any URL, you'll get a single webview in the Right dock lockation rendering the sample html/javascript page that ships with the package. Note that the string "EQUALS" will be replaced by an "=" character in the URL. + +webviewTitlePOS=string + +Set the title of the webview pane to string. + +webviewShowTitlePOS=true or false + +A value of true will show and false will remove the title bar from the webview pane. + +webviewShowProgressPOS=true or false + +Show a progress bar while loading for the web pane. + +webviewSizePOS=integer + +Set the width (for right and left panes) or height (for top and bottom panes) of the web pane. + +An example using all of the above: + +``` + shell> rv -flags ModeManagerPreload=webview \ + webviewUrlRight=file:///foo.html \ + webviewShowTitleRight=false \ + webviewShowProgressRight=false \ + webviewSizeRight=200 \ + webviewUrlBottom=file:///bar.html \ + webviewShowTitleBottom=false \ + webviewShowProgressBottom=false \ + webviewSizeBottom=300 +``` \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-nine.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-nine.md new file mode 100644 index 000000000..2a681e492 --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-nine.md @@ -0,0 +1,318 @@ +# Chapter 9 - Package System + +Use the package system described below to expand RV. + +### 9.1 rvpkg Command Line Tool + + +The rvpkg command line tool makes it possible to manage packages from the shell. If you use rvpkg you do not need to use RV's preferences UI to install/uninstall add/remove packages from the file system. We recommend using this tool instead of manually editing files to prevent the necessity of keeping abreast of how all the state is stored in new versions.The rvpkg tool can perform a superset of the functions available in RV's packages preference user interface. + +| | | +| --- | --- | +| -include directory | include directory as if part of RV_SUPPORT_PATH | +| -env | show RV_SUPPORT_PATH include app areas | +| -only directory | use directory as sole content of RV_SUPPORT_PATH | +| -add directory | add packages to specified support directory | +| -remove | remove packages (by name, rvpkg name, or full path to rvpkg) | +| -install | install packages (by name, rvpkg name, or full path to rvpkg) | +| -uninstall | uninstall packages (by name, rvpkg name, or full path to rvpkg) | +| -optin | opt-in (load) now on behalf of all users, so it will be as if they opted in | +| -list | list installed packages | +| -info | detailed info about packages (by name, rvpkg name, or full path to rvpkg) | +| -force | Assume answer is 'y' to any confirmations – don't be interactive | + +Table 9.1: rvpkg Options + +**Note** : many of the below commands, including install, uninstall, and remove will look for the designated packages in the paths in the RV_SUPPORT_PATH environment variable. If the package you want to operate on is not in a path listed there, that path can be added on the command line with the -include option. + +#### 9.1.1 Getting a List of Available Packages + +``` + shell> rvpkg -list +``` +Lists all packages that are available in the RV_SUPPORT_PATH directories. Typical output from rvpkg looks like this: + +``` +I L - 1.7 "Annotation" /SupportPath/Packages/annotate-1.7.rvpkg +I L - 1.1 "Documentation Browser" /SupportPath/Packages/doc_browser-1.1.rvpkg +I - O 1.1 "Export Cuts" /SupportPath/Packages/export_cuts-1.1.rvpkg +I - O 1.3 "Missing Frame Bling" /SupportPath/Packages/missing_frame_bling-1.3.rvpkg +I - O 1.4 "OS Dependent Path Conversion" /SupportPath/Packages/os_dependent_path_conversion_mode-1.4.rvpkg +I - O 1.1 "Nuke Integration" /SupportPath/Packages/rvnuke-1.1.rvpkg +I - O 1.2 "Sequence From File" /SupportPath/Packages/sequence_from_file-1.2.rvpkg +I L - 1.3 "Session Manager" /SupportPath/Packages/session_manager-1.3.rvpkg +I L - 2.2 "RV Color/Image Management" /SupportPath/Packages/source_setup-2.2.rvpkg +I L - 1.3 "Window Title" /SupportPath/Packages/window_title-1.3.rvpkg +``` +The first three columns indicate installation status (I), load status (L), and whether or not the package is optional (O).If you want to include a support path directory that is not in RV_SUPPORT_PATH, you can include it like this: + +``` + shell> rvpkg -list -include /path/to/other/support/area +``` +To limit the list to a single support area: + +``` + shell> rvpkg -list -only /path/to/area +``` +The -include and -only arguments may be applied to other options as well. + +#### 9.1.2 Getting Information About the Environment + +You can see the entire support path list with the command: + +``` + shell> rvpkg -env +``` +This will show alternate version package areas constructed from the RV_SUPPORT_PATH environment variable to which packages maybe added, removed, installed and uninstalled. The list may differ based on the platform. + +#### 9.1.3 Getting Information About a Package + +``` + shell> rvpkg -info /path/to/file.rvpkg +``` +This will result in output like: + +``` +Name: Window Title +Version: 1.3 +Installed: YES +Loadable: YES +Directory: +Author: Tweak Software +Organization: Tweak Software +Contact: an actual email address +URL: http://www.tweaksoftware.com +Requires: +RV-Version: 3.9.11 +Hidden: YES +System: YES +Optional: NO +Writable: YES +Dir-Writable: YES +Modes: window_title +Files: window_title.mu +``` + +#### 9.1.4 Adding a Package to a Support Area + +``` + shell> rvpkg -add /path/to/area /path/to/file1.rvpkg /path/to/file2.rvpkg +``` +You can add multiple packages at the same time. Remember that adding a package makes it become available for installation, it does not install it. + +#### 9.1.5 Removing a Package from a Support Area + +``` + shell> rvpkg -remove /path/to/area/Packages/file1.rvpkg +``` +Unlike adding, the package in this case is the one in the support area's Packages directory. You can remove multiple packages at the same time.If the package is installed rvpkg will interactively ask for confirmation to uninstall it first. You can override that by using -force as the first argument: + +``` + shell> rvpkg -force -remove /path/to/area/Packages/file1.rvpkg +``` + +#### 9.1.6 Installing and Uninstalling Available Packages + +``` + shell> rvpkg -install /path/to/area/Packages/file1.rvpkg +shell> rvpkg -uninstall /path/to/area/Packages/file1.rvpkg +``` +If files are missing when uninstalling rvpkg may complain. This can happen if multiple versions where somehow installed into the same area. + +#### 9.1.7 Combining Add and Install for Automated Installation + +If you're using rvpkg from an automated installation script you will want to use the -force option to prevent the need for interaction. rvpkg will assume the answer to any questions it might ask is “yes”. This will probably be the most common usage: + +``` + shell> rvpkg -force -install -add /path/to/area /path/to/some/file1.rvpkg +``` +Multiple packages can be specified with this command. All of the packages are installed into /path/to/area.To force uninstall followed by removal: + +``` + shell> rvpkg -force -remove /path/to/area/Packages/file1.rvpkg +``` +The -uninstall option is unnecessary in this case. + +#### 9.1.8 Overrideing Default Optional Package Load Behavior + +If you want optional packages to be loaded by default for all users, you can do the following: + +``` + shell> rvpkg -optin /path/to/area/Packages/file1.rvpkg +``` +In this case, rvkpg will rewrite the rvload2 file associated with the support area to indicate the package is no longer optional. The user can still unload the package if they want, but it will be loaded by default after running the command. + +### 9.2 Package File Contents + + +A package file is zip file with at least one special file called PACKAGE along with .mu, .so, .dylib, and support files (plain text, images, icons, etc) which implement the actual package.Creating a package requires the zip binary. The zip binary is usually part of the default install on each of the OSes that RV runs on.The contents of the package should NOT be put in a parent directory before being zipped up. The PACKAGE manifest as well as any other files should be at the root level of the zip file.When a package is installed, RV will place all of its contents into subdirectories in one of the RV_SUPPORT_PATH locations. If the RV_SUPPORT_PATH is not defined in the environment, it is assumed to have the value of RV_HOME/plugins followed by the home directory support area (which varies with each OS: see the user manual for more info). Files contained in one zip file will all be under the same support path directory; they will not be installed distributed over more than one support path location.The install locations of files in the zip file is described in a filed called PACKAGE which must be present in the zip file. The minimum package file contains two files: PACKAGE and one other file that will be installed. A package zip file must reside in the subdirectory called Packages in one of the support path locations in order to be installed. When the user adds a package in the RV package manager, this is where the file is copied to. + +### 9.3 PACKAGE Format + + +The PACKAGE file is a [YAML](http://www.yaml.org/) file providing information about how the package is used and installed as well as user documentation. Every package must have a PACKAGE file with an accurate description of its contents.The top level of the file may contain the following fields: + +| Field | Value Type | Required | Description | +| --- | --- | --- | --- | +| package | string | • | The name of the package in human readable form | +| author | string | | The name of the author/creator of the package | +| organization | string | | The name of the organization (company) the author created the package for | +| contact | email address | | The email contact of the author/support person | +| version | version number | • | The package version | +| url | URL | | Web location for the package where updates, additional documentation resides | +| rv | version number | • | The minimum version of commercial RV which this package is compatible with | +| openrv | version number | • | The minimum version of Open RV which this package is compatible with | +| requires | zip file name list | | Any other packages (as zip file names) which are required in order to install/load this package | +| icon | PNG file name | | The name of an file with an icon for this package | +| imageio | file list | | List of files in package which implement Image I/O | +| movieio | file list | | List of files in package which implement Movie I/O | +| hidden | boolean | | Either “true” or “false” indicating whether package should be visible by default in the package manager | +| system | boolean | | Either “true” or “false” indicating whether the package was pre-installed with RV and cannot be removed/uninstalled | +| optional | boolean | | Either “true” or “false” indicating whether the package should appear loaded by default. If true the package is not loaded by default after it is installed. Typically this is used only for packages that are pre-installed. (Added in 3.10.9) | +| modes | YAML list | | List of modes implemented in the package | +| files | YAML list | | List non-mode file handling information | +| description | HTML 1.0 string | • | HTML documentation of the package for user viewing in the package manager | + +Table 9.2:Top level fields of PACKAGE file.Each element of the modes list describes one Mu module which is implemented as either a .mu file or a .so file. Files implementing modes are assumed to be Mu module files and will be placed in the Mu subdirectory of the support path location. The other fields are used to optionally create a menu item and/or a short cut key either of which will toggle the mode on/off. The load field indicates when the mode should be loaded: if the value is “delay” the mode will be loaded the first time it is activated, if the value is “immediate” the mode will be loaded on start up. + +| Field | Value Type | Required | Description | +| --- | --- | --- | --- | +| file | string | • | The name of the file which implements the mode | +| menu | string | | If defined, the string which will appear in a menu item to indicate the status (on/off) of the mode | +| shortcut | string | | If defined and menu is defined the shortcut for the menu item | +| event | string | | Optional event name used to toggle mode on/off | +| load | string | • | Either immediate or delay indicating when the mode should be loaded | +| icon | PNG image file | | Icon representing the mode | +| requires | mode file name list | | Names of other mode files required to be active for this mode to be active | + +Table 9.3:Mode Fields + +As an example, the package window_title-1.0.rvpkg has a relatively simple PACKAGE file shown here: + +``` +package: Window Title +author: Tweak Software +organization: Tweak Software +contact: some email address of the usual form +version: 1.0 +url: http://www.tweaksoftware.com +rv: 3.6 +requires: '' + +modes: + - file: window_title + load: immediate + +description: | + +

This package sets the window title to something that indicates the + currently viewed media. +

+ +

How It Works

+ +

The events play-start, play-stop, and frame-changed, are bound to + functions which call setWindowTitle().

+``` +When the package zip file contains additional support files (which are not specified as modes) the package manager will try to install them in locations according to the file type. However, you can also directly specify where the additional files go relative to the support path root directory. + +| | | | | +| --- | --- | --- | --- | +| Field | Value Type | Required | Description | +| file | string | • | The name of the file in the package zip file | +| location | string | • | Location to install file in relative to the support path root. This can contain the variable $PACKAGE to specify special package directories. E.g. SupportFiles/$PACKAGE is the support directory for the package. | + +Table 9.4:File FieldsFor example if you package contains icon files for user interface, they can be forced into the support files area of the package like this: + +``` + files: + - file: myicon.tif + location: SupportFiles/$PACKAGE +``` + +### 9.4 Package Management Configuration Files + + +There are two files which the package manager creates and uses: rvload2 (previous releases had a file called rvload) in the Mu subdirectory and rvinstall in the Packages subdirectory. rvload2 is used on start up to load package modes and create stubs in menus or events for toggling the modes on/off if they are lazy loaded. rvinstall lists the currently known package zip files with a possible an asterisk in front of each file that is installed. The rvinstall file in used only by the package manager in the preferences to keep track of which packages are which.The rvload2 file has a one line entry for each mode that it knows about. This file is automatically generated by the package manager when the user installs a package with modes in it. The first line of the file indicates the version number of the rvload2 file itself (so we can change it in the future) followed by the one line descriptions.For example, this is the contents of rvload2 after installing the window title package: + +``` +3 +window_title,window_title.zip,nil,nil,nil,true,true,false +``` +The fields are: + +1. The mode name (as it appears in a require statement in Mu) +2. The name of the package zip file the mode originally comes from +3. An optional menu item name +4. An optional menu shortcut/accelerator if the menu item exists +5. An optional event to bind mode toggling to +6. A boolean indicating whether the mode should be loaded immediately or not +7. A boolean indicating whether the mode should be activated immediately +8. A boolean indicating whether the mode is optional so it should not be loaded by default unless the user opts-in. + +Each field is separated by a comma and there should be no extra whitespace on the line. The rvinstall file is much simpler: it contains a single zip file name on each line and an asterisk next to any file which is current known to be installed. For example: + +``` +crop.zip +layer_select.zip +metadata_info.zip +sequence_from_file.zip +*window_title.zip +``` +In this case, five modes would appear in the package manager UI, but only the window title package is actually installed. The zip files should exist in the same directory that rvinstall lives in. + +### 9.5 Developing a New Package + + +In order to start a new package there is a chicken and egg problem which needs to be overcome: the package system wants to have a package file to install.The best way to start is to create a source directory somewhere (like your source code repository) where you can build the zip file form its contents. Create a file called PACKAGE in that directory by copying and pasting from either this manual (listing [9.3](#example-PACKAGE) ) or from another package you know works and edit the file to reflect what you will be doing (i.e. give it a name, etc).If you are writing a Mu module implementing a mode or widget (which is also a mode) then create the .mu file in that directory also.You can at that point use zip to create the package like so: + +``` + shell> zip new_package-0.0.rvpkg PACKAGE the_new_mode.mu +``` +This will create the new_package-0.0.rvpkg file. At this point you're ready to install your package that doesn't do anything. Open RV's preferences and in the package manager UI add the zip file and install it (preferably in your home directory so it's visible only to you while you implement it).Once you've done this, the rvload2 and rvinstall files will have been either created or updated automatically. You can then start hacking on the installed version of your Mu file (not the one in the directory you created the zip file in). Once you have it working the way you want copy it back to your source directory and create the final zip file for distribution and delete the one that was added by RV into the Packages directory. + + +#### 9.5.2 Using the Mode Manager While Developing + +It's possible to delay making an actual package file when starting development on individual modes. You can force RV to load your mode (assuming it's in the MU_MODULE_PATH someplace) like so: + +``` + shell> rv -flags ModeManagerLoad=my_new_mode +``` +where my_new_mode is the name of the .mu file with the mode in it (without the extension).You can get verbose information on what's being loaded and why (or why not by setting the verbose flag): + +``` + shell> rv -flags ModeManagerVerbose +``` +The flags can be combined on the command line. + +``` + shell> rv -flags ModeManagerVerbose ModeManagerLoad=my_new_mode +``` +If your package is installed already and you want to force it to be loaded (this overrides the user preferences) then: + +``` + shell> rv -flags ModeManagerPreload=my_already_installed_mode +``` +similarly, if you want to force a mode not to be loaded: + +``` + shell> rv -flags ModeManagerReject=my_already_installed_mode +``` + +#### 9.5.3 Using -debug mu + +Normally, RV will compile Mu files to conserve space in memory. Unfortunately, that means loosing a lot of information like source locations when exceptions are thrown. You can tell RV to allow debugging information by adding -debug mu to the end of the RV command line. This will consume more memory but report source file information when displaying a stack trace. + +#### 9.5.4 The Mu API Documentation Browser + +The Mu modules are documented dynamically by the documentation browser. This is available under RV's help menu “Mu API Documentation Browser”. + +### 9.6 Loading Versus Installing and User Override + + +The package manager allows each user to individually install and uninstall packages in support directories that they have permission in. For directories that the user does not have permission in the package manager maintains a separate list of packages which can be excluded by the user.For example, there may be a package installed facility wide owned by an administrator. The support directory with facility wide packages only allows read permission for normal users. Packages that were installed and loaded by the administrator will be automatically loaded by all users.In order to allow a user to override the loading of system packages, the package manager keeps a list of packages not to load. This is kept in the user's preferences file (see user manual for location details). In the package manager UI the “load” column indicates the user status for loading each package in his/her path. + +#### 9.6.1 Optional Packages + +The load status of optional packages are also kept in the user's preferences, however these packages use a different preference variable to determine whether or not they should be loaded. By default optional packages are not loaded when installed. A package is made optional by setting the \`\`optional'' value in the PACKAGE file to true. \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-one.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-one.md new file mode 100644 index 000000000..eee24d590 --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-one.md @@ -0,0 +1,36 @@ +# Chapter 1 - Overview + +RV comes with the source code to its user interface. The code is written in a language called Mu which is not difficult to learn if you know Python, MEL, or most other computer languages used for computer graphics. RV can use Python in a nearly interchangeable manner. If you are completely unfamiliar with programming, you may still glean information about how to customize RV in this manual; but the more complex tasks like creating a special overlay or slate for RVIO or adding a new heads-up widget to RV might be difficult to understand without help from someone more experienced.This manual does not assume you know Mu to start with, so you can dive right in. For Python, some assumptions are made. The chapters are organized with specific tasks in mind.The reference chapters contain detailed information about various internals that you can modify from the UI.Using the RV file format (.rv) is detailed in Chapter [6](rv-reference-manual-chapter-six.md#chapter-6-rv-file-format). + + +### 1.1 The Big Picture + +RV is two different pieces of software: the core (written in C++) and the interface (written in Mu and Python). The core handles the following things: + +* Image, Movie, and Audio I/O +* Caching Images and Audio +* Tracking Dependencies Among Image and Audio Operations +* Basic Image Processing in Software +* Rendering Images +* Feeding Audio to Audio Output Devices + +The interface — which is available to be modified — is concerned with the following: + +* Handling User Events +* Rendering Additional Information and Heads-up Widgets +* Setting and Getting State in the Image Processing Graph +* Interfacing to the Environment +* Handling User Defined Setup of Incoming Movies/Images/Audio +* High Level Features + +RVIO shares almost everything with RV including the UI code (if you want it to). However it will not launch a GUI so its UI is normally non-existent. RVIO does have additional hooks for modification at the user level: overlays and leaders. Overlays are Mu scripts which allow you to render additional visual information on top of rendered images before RVIO writes them out. Leaders are scripts which generate frames from scratch (there is nothing rendered under them) and are mainly there to generate customized flexible slates automatically. + +### 1.2 Drawing + + +In RV's user interface code or RVIO's leader and overlays it's possible draw on top of rendered frames. This is done using the industry standard API OpenGL. There are Mu modules which implement OpenGL 1.1 functions including the GLU library. In addition, there is a module which makes it easy to render true type fonts as textures (so you can scale, rotate, and composite characters as images). For Python there is PyOpenGL and related modules.Mu has a number of OpenGL friendly data types which include native support for 2D and 3D vectors and dependently typed matrices (e.g., float[4,4], float[3,3], float[4,3], etc). The Mu GL modules take the native types as input and return them from functions, but you can use normal GL documentation and man pages when programming Mu GL. In this manual, we assume you are already familiar with OpenGL. There are many resources available to learn it in a number of different programming languages. Any of those will suffice to understand it. + +### 1.3 Menus + + +The menu bar in an RV session window is completely controlled (and created) by the UI. There are a number of ways you can add menus or override and replace the existing menu structure.Adding one or more custom menus to RV is a common customization. This manual contains examples of varying complexity to show how to do this. It is possible to create static menus (pre-defined with a known set of menu items) or dynamic menus (menus that are populated when RV is initialized based on external information, like environment variables). \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-seven.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-seven.md new file mode 100644 index 000000000..be38dc3bb --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-seven.md @@ -0,0 +1,121 @@ +# Chapter 7 - Using Qt in Mu + +You can browse the Qt and other Mu modules with the documentation browser. RV wraps the Qt API. Using Qt in Mu is similar to using it in C++. Each Qt class is presented as a Mu class which you can either use directly or inherit from if need be. However, there are some major differences that need to be observed: + +> **Note:** Python code can assume that default parameters values will be supplied if not specified. + +* Not all Qt classes are wrapped in Mu. It's a good idea to look in the documentation browser to see if a class is available yet. +* Property names in C++ do not always match those in Mu. Mu collects Qt properties at runtime in order to provide limited supported for unknown classes. So the set and get functions for the properties are generated at that time. Usually these names match the C++ names, but sometimes there are differences. In general, the Mu function to get a property called **foo** will be called foo(). The Mu function to set the foo property will be called setFoo(). (A good example of this is the QWidget property **visible** . In C++ the get function is isVisible() whereas the Mu function is called visible().) +* Templated classes in Qt are not available in Mu. Usually these are handled by dynamic array types or something analogous to the Qt class. In the case of template member functions (like QWidget::findChild<>) there may be an equivalent Mu version that operates slightly differently (like the Mu version QWidget.findChild). +* The QString class is not wrapped (yet). Instead, the native Mu string can be used anywhere a function takes a QString. +* You cannot control widget destruction. If you loose a reference to a QObject it will eventually be finalized (destroyed), but at an unknown time. +* Some classes cannot be inherited from. You can inherit from any QObject, QPainter, or QLayoutItem derived class except QWebFrame and QNetworkReply. +* The signal slot mechanism is slightly different in Mu than C++. It is currently not possible to make a new Qt signal, and slots do not need to be declared in a special way (but they do need to have the correct signatures to be connected). In addition, you are not required to create a QObject class to receive a signal in Mu. You can also connect a signal directly to a regular function if desired (as opposed to class member functions in C++). +* Threading is not yet available. The QThread class cannot be used in Mu yet. +* Abstract Qt classes can be instantiated. However, you can't really do anything with them. +* Protected member functions are public. + +### 7.1 Signals and Slots + + +Possibly the biggest difference between the Mu and C++ Qt API is how signals and slots are handled. This discussion will assume knowledge of the C++ mechanism. See the Qt documentation if you don't know what signals and slots are.Jumping right in, here is an example hello world MuQt program. This can be run from the mu-interp binary: + +``` + use qt; + +\: clicked (void; bool checked) +{ + print("OK BYE\n"); + QCoreApplication.exit(0); +} + +\: main () +{ + let app = QApplication(string[] {"hello.mu"}), + window = QWidget(nil, Qt.Window), + button = QPushButton("MuQt: HELLO WORLD!", window); + + connect(button, QPushButton.clicked, clicked); + + window.setSize(QSize(200, 50)); + window.show(); + window.raise(); + QApplication.exec(); +} + +main(); + +``` +The main thing to notice in this example is the connect() function. A similar C++ version of this would look like this: + +``` + connect(button, SIGNAL(clicked(bool)), SLOT(myclickslot(bool))); +``` +where myclickslot would be a slot function declared in a class. In Mu it's not necessary to create a class to receive a signal. In addition the SIGNAL and SLOT syntax is also unnecessary. However, it is necessary to exactly specify which signal is being referred to by passing its Mu function object directly. In this case QPushButton.clicked. The signal must be a function on the class of the first argument of connect().In Mu, any function which matches the signal's signature can be used to receive the signal. The downside of this is that some functions like sender() are not available in Mu. However this is easily overcome with partial application. In the above case, if we need to know who sent the signal in our clicked function, we can change its signature to accept the sender and partially apply it in the connect call like so: + +``` +\: clicked (void; bool checked, QPushButton sender) +{ + // do something with sender +} + +\: main () +{ + ... + + connect(button, QPushButton.clicked, clicked(,button)); +} + +``` +And of course additional information can be passed into the clicked function by applying more arguments.It's also possible to connect a signal to a class method in Mu if the method signature matches. Partial application can be used in that case as well. This is frequently the case when writing a mode which uses Qt interface. + +### 7.2 Inheriting from Qt Classes + + +It's possible to inherit directly from the Qt classes in Mu and override methods. Virtual functions in the C++ version of Qt are translated as class methods in Mu. Non-virtual functions are regular functions in the scope of the class. In practice this means that the Mu Qt class usage is very similar to the C++ usage.The following example shows how to create a new widget type that implements a drop target. Drag and drop is one aspect of Qt that requires inheritance (in C++ and Mu): + +``` +use qt; + +class: MyWidget : QWidget +{ + method: MyWidget (MyWidget; QObject parent, int windowFlags) + { + // REQUIRED: call base constructor to build Qt native object + QWidget.QWidget(this, parent, windowFlags); + setAcceptDrops(true); + } + + method: dragEnterEvent (void; QDragEnterEvent event) + { + print("drop enter\n"); + event.acceptProposedAction(); + } + + method: dropEvent (void; QDropEvent event) + { + print("drop\n"); + let mimeData = event.mimeData(), + formats = mimeData.formats(); + + print("--formats--\n"); + for_each (f; formats) print("%s\n" % f); + + if (mimeData.hasUrls()) + { + print("--urls--\n"); + for_each (u; event.mimeData().urls()) + print("%s\n" % u.toString(QUrl.None)); + } + + if (mimeData.hasText()) + { + print("--text--\n"); + print("%s\n" % mimeData.text()); + } + + event.acceptProposedAction(); + } +} +``` +Things to note in this example: the names of the drag and drop methods matter. These are same names as used in C++. If you browser the documentation of a Qt class in Mu these will be the class methods. Only class methods can be overridden. \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-seventeen.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-seventeen.md new file mode 100644 index 000000000..c786a60d8 --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-seventeen.md @@ -0,0 +1,206 @@ +# Chapter 17 - Additional GLSL Node Reference + +This chapter describes the list of GLSL custom nodes that come bundled with RV. These nodes are grouped into five sections within this chapter based on the nodes "evaluationType" i.e. color, filter, transition, merge or combine. Each sub-section within a section describes a node and its parameters. For a complete description of the GLSL custom node itself, refer to the chapter on that topic i.e. "Chapter 3: Writing a Custom GLSL Node".The complete collection of GLSL custom nodes that come with each RV distribution are stored in the following two files located at: + +``` + Linux & Windows: +/plugins/Nodes/AdditionalNodes.gto +/plugins/Support/additional_nodes/AdditionalNodes.zip + +Mac: +/Contents/PlugIns/Nodes/AdditionalNodes.gto +/Contents/PlugIns/Support/additional_nodes/AdditionalNodes.zip +``` +The file "AdditionalNodes.gto" is a GTO formatted text file that contains the definition of all the nodes described in this chapter. All of the node definitions found in this file are signed for use by all RV4 versions. The GLSL source code that implements the node's functionality is embedded within the node definition's function block as an inlined string. In addition, the default values of the node's parameters can be found within the node definition's parameter block. The accompanying support file "AdditionalNodes.zip" is a zipped up collection of individually named node ".gto" and ".glsl" files. Users can unzip this package and refer to each node's .gto/.glsl file as examples of custom written RV GLSL nodes. Note the file "AdditionalNodes.zip" is not used by RV. Instead RV only uses "AdditionalNodes.gto" which was produced from all the files found in "AdditionalNodes.zip".These nodes can be applied through the session manager to sources, sequences, stacks, layouts or other nodes. First you select a source (for example) and from the session manager "+" pull menu select "New Node by Type" and type in the name of the node in the entry box field of the "New Node by Type" window. + +### 17.1 Color Nodes + + +This section describes all the GLSL nodes of evaluationType "color" found in "AdditionalNodes.gto". + +#### 17.1.1 Matrix3x3 + +This node implements a 3x3 matrix multiplication on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Input parameters: + +| Property | Type | Default | +| --- | --- | --- | +| node.parameters.m33 | float[9] | [ 1 0 0 0 1 0 0 0 1 ] | + +#### 17.1.2 Matrix4x4 + +This node implements a 4x4 matrix multiplication on the RGBA channels of the inputImage. The inputImage alpha channel is affected by this node.Input parameters: + +| Property | Type | Default | +| --- | --- | --- | +| node.parameters.m44 | float[16] | [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] | + +#### 17.1.3 Premult + +This node implements the "premultiply by alpha" operation on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.4 UnPremult + +This node implements the "unpremultiply by alpha" (i.e. divide by alpha) operation on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.5 Gamma + +This node implements the gamma (i.e. pixelColor^gamma) operation on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node. Input parameters: + +| Property | Type | Default | +| --- | --- | --- | +| node.parameters.gamma | float[3] | [ 0.4545 0.4545 0.4545 ] | + +#### 17.1.6 CDL + +This node implements the Color Description List operation on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Parameter lumaCoefficients defaults to full range Rec709 luma values.Input parameters: + +| Property | Type | Default | +| --- | --- | --- | +| node.parameters.slope | float[3] | [ 1 1 1 ] | +| node.parameters.offset | float[3] | [ 0 0 0 ] | +| node.parameters.power | float[3] | [ 1 1 1 ] | +| node.parameters.saturation | float | [ 1 ] | +| node.parameters.lumaCoefficients | float[3] | [ 0.2126 0.7152 0.0722 ] | +| node.parameters.minClamp | float | [ 0 ] | +| node.parameters.maxClamp | float | [ 1 ] | + +#### 17.1.7 CDLForACESLinear + +This node implements the Color Description List operation in ACES linear colorspace on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Parameter lumaCoefficients defaults to full range Rec709 luma values.If the inputImage colorspace is NOT in ACES linear, but in some X linear colorspace; then one must set the 'toACES' property to the X-to-ACES colorspace conversion matrix and similarly the 'fromACES' property to the ACES-to-X colorspace conversion matrix.Input parameters: + +| Property | Type | Default | +| --- | --- | --- | +| node.parameters.slope | float[3] | [ 1 1 1 ] | +| node.parameters.offset | float[3] | [ 0 0 0 ] | +| node.parameters.power | float[3] | [ 1 1 1 ] | +| node.parameters.saturation | float | [ 1 ] | +| node.parameters.lumaCoefficients | float[3] | [ 0.2126 0.7152 0.0722 ] | +| node.parameters.toACES | float[16] | [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] | +| node.parameters.fromACES | float[16] | [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] | +| node.parameters.minClamp | float | [ 0 ] | +| node.parameters.maxClamp | float | [ 1 ] | + +#### 17.1.8 CDLForACESLog + +This node implements the Color Description List operation in ACES Log colorspace on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Parameter lumaCoefficients defaults to full range Rec709 luma values.If the inputImage colorspace is NOT in ACES linear, but in some X linear colorspace; then one must set the 'toACES' property to the X-to-ACES colorspace conversion matrix and similarly the 'fromACES' property to the ACES-to-X colorspace conversion matrix.Input parameters: + +| Property | Type | Default | +| --- | --- | --- | +| node.parameters.slope | float[3] | [ 1 1 1 ] | +| node.parameters.offset | float[3] | [ 0 0 0 ] | +| node.parameters.power | float[3] | [ 1 1 1 ] | +| node.parameters.saturation | float | [ 1 ] | +| node.parameters.lumaCoefficients | float[3] | [ 0.2126 0.7152 0.0722 ] | +| node.parameters.toACES | float[16] | [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] | +| node.parameters.fromACES | float[16] | [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] | +| node.parameters.minClamp | float | [ 0 ] | +| node.parameters.maxClamp | float | [ 1 ] | + +#### 17.1.9 SRGBToLinear + +This linearizing node implements the sRGB to linear transfer function operation on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.10 LinearToSRGB + +This node implements the linear to sRGB transfer function operation on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.11 Rec709ToLinear + +This linearizing node implements the Rec709 to linear transfer function operation on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.12 LinearToRec709 + +This node implements the linear to Rec709 transfer function operation on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.13 CineonLogToLinear + +This linearizing node implements the Cineon Log to linear transfer function operation on the RGB channels of the inputImage. The implementation is based on Kodak specification "The Cineon Digital Film System". The inputImage alpha channel is not affected by this node.Input parameters: (values must be specified within the range [0..1023]) + +| Property | Type | Default | +| --- | --- | --- | +| node.parameters.refBlack | float | 95 | +| node.parameters.refWhite | float | 685 | +| node.parameters.softClip | float | 0 | + +#### 17.1.14 LinearToCineonLog + +This node implements the linear to Cineon Log film transfer function operation on the RGB channels of the inputImage. The implementation is based on Kodak specification "The Cineon Digital Film System". The inputImage alpha channel is not affected by this node.Input parameters: (values must be specified within the range [0..1023]) + +| Property | Type | Default | +| --- | --- | --- | +| node.parameters.refBlack | float | 95 | +| node.parameters.refWhite | float | 685 | + +#### 17.1.15 ViperLogToLinear + +This linearizing node implements the Viper Log to linear transfer function operation on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.16 LinearToViperLog + +This node implements the linear to Viper Log transfer function operation on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.17 RGBToYCbCr601 + +This node implements the RGB to YCbCr 601 conversion operation on the RGB channels of the inputImage. Implementation is based on ITU-R BT.601 specification. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.18 RGBToYCbCr709 + +This node implements the RGB to YCbCr 709 conversion operation on the RGB channels of the inputImage. Implementation is based on ITU-R BT.709 specification. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.19 RGBToYCgCo + +This node implements the RGB to YCgCo conversion operation on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.20 YCbCr601ToRGB + +This node implements the YCbCr 601 to RGB conversion operation on the RGB channels of the inputImage. Implementation is based on ITU-R BT.601 specification. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.21 YCbCr709ToRGB + +This node implements the YCbCr 709 to RGB conversion operation on the RGB channels of the inputImage. Implementation is based on ITU-R BT.709 specification. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.22 YCgCoToRGB + +This node implements the YCgCo to RGB conversion operation on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.23 YCbCr601FRToRGB + +This node implements the YCbCr 601 "Full Range" to RGB conversion operation on the RGB channels of the inputImage. Implementation is based on ITU-R BT.601 specification. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.24 RGBToYCbCr601FR + +This node implements the RGB to YCbCr 601 "Full Range" conversion operation on the RGB channels of the inputImage. Implementation is based on ITU-R BT.601 specification. The inputImage alpha channel is not affected by this node.Input parameters: None + +#### 17.1.27 Saturation + +This node implements the saturation operation on the RGB channels of the inputImage. The inputImage alpha channel is not affected by this node.Parameter lumaCoefficients defaults to full range Rec709 luma values.Input parameters: + +| Property | Type | Default | +| --- | --- | --- | +| node.parameters.saturation | float | [ 1 ] | +| node.parameters.lumaCoefficients | float[3] | [ 0.2126 0.7152 0.0722 ] | +| node.parameters.minClamp | float | [ 0 ] | +| node.parameters.maxClamp | float | [ 1 ] | + +### 17.2 Transition Nodes + + +This section describes all the GLSL nodes of evaluationType "transition" found in "AdditionalNodes.gto". + +### 17.2.1 CrossDissolve + +This node implements a simple cross dissolve transition effect on the RGBA channels of two inputImage sources beginning from startFrame until (startFrame + numFrames -1). The inputImage alpha channel is affected by this node.Input parameters: + +| Property | Type | Default | +| --- | --- | --- | +| node.parameters.startFrame | float | 40 | +| node.parameters.numFrame | float | 20 | + +### 17.2.2 Wipe + +This node implements a simple wipe transition effect on the RGBA channels of two inputImage sources beginning from startFrame until (startFrame + numFrames -1). The inputImage alpha channel is affected by this node.Input parameters: + +| Property | Type | Default | +| --- | --- | --- | +| node.parameters.startFrame | float | 40 | +| node.parameters.numFrame | float | 20 | \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-six.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-six.md new file mode 100644 index 000000000..b2a0a1cd6 --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-six.md @@ -0,0 +1,563 @@ +# Chapter 6 - Open RV File Format + +The RV file format (.rv) is a text GTO file. GTO is an open source file format which stores arbitrary data — mostly for use in computer graphics applications. The text GTO format is meant to be simple and human readable. It's helpful to have familiarized yourself with the GTO documentation before reading this section. The documentation should come with RV, or you can read it on line at the [GTO](../../rv-manuals/rv-gto.md). + +### 6.1 How Open RV Uses GTO + +RV defines a number of new GTO object protocols (types of objects). The GTO file is made up of objects, which contain components, which contain properties where the actual data resides. RV's use of the format is to store nodes in an image processing graph as GTO objects. How the nodes are connected is determined by RV and is not currently arbitrary so there are no connections between the objects stored in the file.Some examples of RV object types include: + +* The **RVSession** object (one per file) which stores information about the session. This includes the full frame range, currently marked frames, the playback FPS, and whether or not to use real time playback among other things. +* **RVLayoutGroup** , **RVFolderGroup, RVSwitchGroup, RVSourceGroup** , **RVRetimeGroup** , **RVStackGroup** , **RVDisplayGroup** and **RVSequenceGroup** nodes which form the top-level of the image processing graph. +* One or more **RVFileSource** objects each within an **RVSourceGroup** which specify all of the media (movies, audio files, image sequences) which are available in the session. +* Color correction objects like **RVColor** nodes which are members of **RVSourceGroup** objects. +* Image format objects like **RVFormat** or **RVChannelMap** which are also members of **RVSourceGroup** objects. +* An **RVDisplayColor** object (one per file) which indicates monitor gamma, any display LUT being used (and possibly the actual LUT data) which is part of the **RVDisplayGroup** . +* A **connections** object which contains connections between the top-level group nodes. The file only stores the top-level connections — connections within group nodes are determined by the group node at runtime. + +Normally, RV will write out all objects to the session file, but it does not require all of them to create a session from scratch. For example, if you have a file with a single RVFileSource object in it, RV will use that and create default objects for everything else. So when creating a file without RV, it's not a bad idea to only include information that you need instead of replicating the output of RV itself. (This helps make your code future proof as well).The order in which the objects appear in the file is not important. You can also include information that RV does not know about if you want to use the file for other programs as well. + +### 6.2 Naming + + +The names of objects in the session are not visible to the user, however they must follow certain naming naming conventions. There is a separate user interface name for top level nodes which the user does see. The user name can be set by creating a string property on a group node called ui.name. + +* If the object is a group node type other than a source or display group its name can be anything, but it must be unique. +* If there is an **RVDisplayGroup** in the file it must be called displayGroup. +* If the object is a member of a group its name should have the pattern: groupName_nodeName where groupName is the name of the group the node is a member of. The nodeName can be anything, but RV will use the type name in lowercase without the “RV” at the front. +* If the object is a **RVFileSourceGroup** or **RVImageSourceGroup** it should be named sourceGroupXXXXXX where the Xs form a six digit zero padded number. RV will create source groups starting with number 000000 and increment the number for each new source group it creates. If a source group is deleted, RV may reuse its number when creating a new group. +* The **connection** object should be named connections. +* The **RVSession** object can have any name. + +### 6.3 A Simple Example + + +The simplest RV file you can create is a one which just causes RV to load a single movie file or image. This example loads a QuickTime file called “test.mov” from the directory RV was started in: + +``` + GTOa (3) + +sourceGroup000000_source : RVFileSource (0) +{ + media + { + string movie = "test.mov" + } +} +``` + +The first line is required for a text GTO file: it indicates the fact that the file is text format and that the GTO file version is 3. All of the other information (the frame ranges, etc) will be automatically generated when the file is read. By default RV will play the entire range of the movie just as if you dropped it into a blank RV session in the UI. You should name the first **RVFileSource** object sourceGroup000000_source and the second sourceGroup000001_source and the third sourceGroup000002_source, and so on. Eventually we'll want to make an EDL which will index the source objects so the names mostly matter (but not the order in which they appear).Now suppose we have an image sequence instead of a movie file. We also have an associated audio file which needs to be played with it. This is a bit more complicated, but we still only need to make a single **RVFileSource** object:Here we've got test.#.dpx as an image layer and soundtrack.aiff which is an audio layer. + +``` + GTOa (3) + +sourceGroup000000_source : RVFileSource (0) +{ + media + { + string movie = [ "test.#.dpx" "soundtrack.aiff" ] + } + + group + { + float fps = 24 + float volume = 0.5 + float audioOffset = 0.1 + } +} +``` +You can have any number of audio and image sequence/movie files in the movie list. All of them together create the output of the **RVFileSource** object. If we were creating a stereo source, we might have left.#.dpx and right.#.dpx instead of test.#.dpx. When there are multiple image layers the first two default to the left and right eyes in the order in which they appear. You can change this behavior per-source if necessary. The format of the various layers do not need to match.The **group** component indicates how all of media should be combined. In this case we've indicated the FPS of the image sequence, the volume of all audio for this source and an audio slip of 0.1 (one tenth) of a second. Keep in mind that FPS here is for the image sequence(s) in the source it has nothing to do with the playback FPS!. The playback FPS is independent of the input sources frame rate. + +#### Aside: What is the FPS of an RVFileSource Object Anyway? + +If you write out an RV file from RV itself, you'll notice that the group FPS is often 0! This is a special cookie value which indicates that the FPS should be taken from the media. Movie file formats like QuickTime or AVI store this information internally. So RV will use the frame rate from the media file as the FPS for the source.However, image sequences typically do not include this information (OpenEXR files are a notable exception).. When you start RV from the command line it will use the playback FPS as a default value for any sources created. If there is no playback FPS on startup either via the command line or preferences, it will default to 24 fps. So it's not a bad idea to include the group FPS when creating an RV file yourself when you're using image sequences. If you're using a movie file format you should either use 0 for the FPS or not include it and let RV figure it out.What happens when you get a mismatch between the source FPS and the playback FPS? If there's no audio, you won't notice anything; RV always plays back every frame in the source regardless of the source FPS. But if you have audio layers along with your image sequence or if the media is a movie file, you will notice that the audio is either compressed or expanded in order to maintain synchronization with the images.This is a very important thing to understand about RV: it will always playback every image no matter what the playback FPS is set to; and it will always change the audio to compensate for that and maintain synchronization with the images.So the source FPS is really important when there is audio associated with the images. + +### 6.4 Per-Source and Display Color Settings and LUT Files + + +If you want to include per-source color information – such as forcing a particular LUT to be applied or converting log to linear – you can include only the additional nodes you need with only the parameters that you wish to pass in. For example, to apply a file LUT to the first source (e.g. sourceGroup000000_source) you can create an **RVColor** node similarly named sourceGroup000000_color. + +``` + sourceGroup000000_color : RVColor (1) +{ + lut + { + string file = "/path/to/LUTs/log2sRGB.csp" + int active = 1 + } +} +``` + +This is a special case in the rv session file: you can refer to a LUT by file. If you have a new-source event bound to a function which modifies incoming color settings based on the image type, any node properties in your session file override the default values created there. To state it another way: values you omit in the session file still exist in RV and will take on whatever values the function bound to new-source made for them. To ensure that you get exactly the color you want you can specify all of the relevant color properties in the **RVColor, RVLinearize,** and **RVDisplayColor** nodes: + +``` + sourceGroup000000_colorPipeline_0 : RVColor (2) +{ + color + { + int invert = 0 + float[3] gamma = [ [ 1 1 1 ] ] + string lut = "default" + float[3] offset = [ [ 0 0 0 ] ] + float[3] scale = [ [ 1 1 1 ] ] + float[3] exposure = [ [ 0 0 0 ] ] + float[3] contrast = [ [ 0 0 0 ] ] + float saturation = 1 + int normalize = 0 + float hue = 0 + int active = 1 + } + + CDL + { + float[3] slope = [ [ 1 1 1 ] ] + float[3] offset = [ [ 0 0 0 ] ] + float[3] power = [ [ 1 1 1 ] ] + float saturation = 1 + int noClamp = 0 + } + + luminanceLUT + { + float lut = [ ] + float max = 1 + int size = 0 + string name = "" + int active = 0 + } + + "luminanceLUT:output" + { + int size = 256 + } +} + +sourceGroup000000_tolinPipeline_0 : RVLinearize (1) +{ + lut + { + float[16] inMatrix = [ [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] ] + float[16] outMatrix = [ [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] ] + float lut = [ ] + float prelut = [ ] + float scale = 1 + float offset = 0 + string type = "Luminance" + string name = "" + string file = "" + int size = [ 0 0 0 ] + int active = 0 + } + + color + { + string lut = "default" + int alphaType = 0 + int logtype = 0 + int YUV = 0 + int invert = 0 + int sRGB2linear = 1 + int Rec709ToLinear = 0 + float fileGamma = 1 + int active = 1 + int ignoreChromaticities = 0 + } + + cineon + { + int whiteCodeValue = 0 + int blackCodeValue = 0 + int breakPointValue = 0 + } + + CDL + { + float[3] slope = [ [ 1 1 1 ] ] + float[3] offset = [ [ 0 0 0 ] ] + float[3] power = [ [ 1 1 1 ] ] + float saturation = 1 + int noClamp = 0 + } +} + +defaultOutputGroup_colorPipeline_0 : RVDisplayColor (1) +{ + lut + { + float[16] inMatrix = [ [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] ] + float[16] outMatrix = [ [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 ] ] + float lut = [ ] + float prelut = [ ] + float scale = 1 + float offset = 0 + string type = "Luminance" + string name = "" + string file = "" + int size = [ 0 0 0 ] + int active = 0 + } + + color + { + string lut = "default" + string channelOrder = "RGBA" + int channelFlood = 0 + int premult = 0 + float gamma = 1 + int sRGB = 0 + int Rec709 = 0 + float brightness = 0 + int outOfRange = 0 + int dither = 0 + int active = 1 + } + + chromaticities + { + int active = 0 + int adoptedNeutral = 0 + float[2] white = [ [ 0.3127 0.329 ] ] + float[2] red = [ [ 0.64 0.33 ] ] + float[2] green = [ [ 0.3 0.6 ] ] + float[2] blue = [ [ 0.15 0.06 ] ] + float[2] neutral = [ [ 0.3127 0.329 ] ] + } +} +``` +The above example values assume default color pipeline slots for a single source session. Please see section [12.3](rv-reference-manual-chapter-twelve.md) to learn more about the specific color pipeline groups. + +### 6.5 Information Global to the Session + + +Now let's add an **RVSession** object with in and output points. The session object should be called rv in this version. There should only be one **RVSession** object in the file. + +> **Note:** From now on we're just going to show fragments of the file and assume that you can put them all together in your text editor. + +``` + rv : RVSession (1) +{ + session + { + string viewNode = "defaultSequence" + int marks = [ 1 20 50 80 100 ] + int[2] range = [ [ 1 100 ] ] + int[2] region = [ [ 20 50 ] ] + float fps = 24 + int realtime = 1 + int currentFrame = 30 + } +} +``` +Assuming this was added to the top of our previous file with the source in it, the session object now indicates the frame range (1-100) and an in and out region (20-50) which is currently active. Frames 1, 20, 50, 80, and 100 are marked and the default frame is frame 30 when RV starts up. The realtime property is a flag which indicates that RV should start playback in real time mode. The view node indicates what will be viewed in the session when the file is opened.Note that it's usually a good idea to skip the frame range boundaries unless an EDL is also specified in the file (which is not the case here). RV will figure out the correct range information from the source media. If you force the range information to be different than the source media's you may get unexpected results. The marks and range can also be stored on each viewable top-level object. For example the defaultLayout and defaultSequence can have different marks and in and out points: + +``` + defaultStack : RVStackGroup (1) +{ + session + { + float fps = 24 + int marks = [ ] + int[2] region = [ [ 100 200 ] ] + int frame = 1 + } +} + +``` +If a group has a session component than the contents can provide an in/out region, marks, playback fps and a current frame. When the user views the group node these values will become inherited by the session. + +### 6.6 The Graph + + +Internally, RV holds a single image processing graph per session which is represented in the session file. The graph can have multiple nodes which determine how the sources are combined. These are the top-level nodes and are always group nodes. The user can create new top-level nodes (like sequences, stacks, layouts, retimings, etc). So the inputs for each node need to be stored in order to reproduce what the user created. The connections between the top-level group nodes are stored in the connections object and includes a list of the top level nodes. For example, this is what RV will write out for a session with a single source in it: + +``` + connections : connection (1) +{ + evaluation + { + string lhs = [ "sourceGroup000000" + "sourceGroup000000" + "sourceGroup000000" ] + + string rhs = [ "defaultLayout" + "defaultSequence" + "defaultStack" ] + } + + top + { + string nodes = [ "sourceGroup00000", + "defaultLayout", + "defaultStack", + "defaultSequence" ] + } +} +``` +The connections should be interpreted as arrows between objects. The lhs (left hand side) is the base of the arrow. The rhs (right hand side) is the tip. The base and tips are stored in separate properties. So in the case, the file has three connections: + +> **Note:** RV may write out a connection to the display group as well. However, that connection is redundant and may be overridden by the value of the view node property in the RVSession. + +1. sourceGroup000000 → defaultLayout +2. sourceGroup000000 → defaultSequence +3. sourceGroup000000 → defaultStack + +The nodes property, if it exists, will determine which nodes are considered top level nodes. Otherwise, nodes which include connections and nodes which have user interface name are considered top level. + +#### 6.6.1 Default Views + +There are three default views that are always created by RV: the default stack, sequence, and layout. Whenever a new source is added by the user each of these will automatically connect the new source as an input. When a new viewing node is created (a new sequence, stack, layout, retime) the default views will not add those —- only sources are automatically added.When writing a .rv file you can co-opt these views to rearrange or add inputs or generate a unique EDL but it's probably a better idea to create a new one instead; RV will never automatically edit a sequence, stack, layout, etc, that is not one of the default views. + +### 6.7 Creating a Session File for Custom Review + + +One of the major reasons to create session files outside of RV is to automatically generate custom review workflows. For example, if you want to look at an old version of a sequence and a new version, you might have your pipeline output a session file with both in the session and have pre-constructed stacked views with wipes and a side-by-side layout of the two sequences.To start with lets look at creating a session file which creates a unique sequence (not the default sequence) with plays back sources in a particular order. In this case, no EDL creation is necessary — we only need to supply the sequence with the source inputs in the correct order. This is analogous to the user reordering the inputs on a sequence in the user interface.This file will have an RVSequenceGroup object as well as the sources. Creating sources is covered above so we'll skip to the creation of the RVSequenceGroup. For this example we'll assume there are three sources and that they all have the same FPS (so no retiming is necessary). We'll let RV handle creation of the underlying RVSequence and its EDL and only create the group: + +``` +// define sources ... + +reviewSequence : RVSequenceGroup (1) +{ + ui { string name = "For Review" } +} + +connections : connection (1) +{ + evaluation + { + string lhs = [ "sourceGroup000002" + "sourceGroup000000" + "sourceGroup000001" ] + + string rhs = [ "reviewSequence" + "reviewSequence" + "reviewSequence" ] + } +} +``` +RV will automatically connect up the default views so we can skip their inputs in the connections object for clarity. In this case, the sequence is connected up so that by default it will play sourceGroup000002 followed by sourceGroup000000 followed by sourceGroup000001 because the default EDL of a sequence just plays back the inputs in order. Note that for basic ordering of playback, no EDL creation is necessary. We could also create additional sequence groups with other inputs. Also note the use of the UI name in the sequence group.Of course, the above is not typical in a production environment. Usually there are handles which need to (possibly) be edited out. There are two ways to do this with RV: either set the cut points in each source and tell the sequence to use them, or create an EDL in the sequence which excludes the handles.To start with we'll show the first method: set the cut points. This method is easy to implement and the sequence interface has a button on it that lets the user toggle the in/out cuts on/off in realtime. If the user reorders the sequence, the cuts will be maintained. When using this method any sequence in the session can be made to use the same cut information — it propagates down from the source to the sequence instead of being stored for each sequence.Setting the cut in/out points requires adding a property to the RVFileSource objects and specifying the in and out frames: + +``` +sourceGroup000000_source : RVFileSource (1) +{ + media { string movie = "shot00.mov" } + + cut + { + int in = 8 + int out = 55 + } +} + +sourceGroup000001_source : RVFileSource (1) +{ + media { string movie = "shot01.mov" } + + cut + { + int in = 5 + int out = 102 + } +} + +sourceGroup000002_source : RVFileSource (1) +{ + media { string movie = "shot02.mov" } + + cut + { + int in = 3 + int out = 22 + } +} + +``` +Finally, the most flexibly way to control playback is to create an EDL. The EDL is stored in an RVSequence node which is a member of the RVSequenceGroup. Whenever an RVSequenceGroup is created, it will create a sequence node to hold the EDL. If you are not changing the default values or behavior of the sequence node it's not necessary to specify it in the file. In this case, however we will be creating a custom EDL. + +#### 6.7.1 RVSequence + +The sequence node can be in one of two modes: auto EDL creation or manual EDL creation. This is controlled by the mode.autoEDL property. If the property is set to 1 then the sequence will behave like so: + +* If a new input is connected, the existing EDL is erased and a new EDL is created. +* Each input of the sequence will have a cut created for it in the order that they appear. If mode.useCutInfo is set, the sequence will use the cut information coming from the input to determine the cut in the EDL. Otherwise it will use the full range of the input. +* If cut info changes on any input to the sequence, the EDL will be adjusted automatically. + +When auto EDL is not on, the sequence node behavior is not well-defined when the inputs are changed. In future, we'd like to provide more interface for EDL modification (editing) but for the moment, a custom EDL should only be created programmatically in the session file.For this next example, we'll use two movie files: a.mov and b.mov. They have audio so there's nothing interesting about their source definitions: just the media property with the name of the movie . They are both 24 fps and the playback will be as well: + +> **Note:** The example RV file has fewer line breaks than one which RV would write. However, it's still valid. + +``` + GTOa (3) + +rv : RVSession (2) +{ + session + { + string viewNode = "mySequence" + } +} + +sourceGroup000000_source : RVFileSource (0) { media { string movie = "a.mov" } } +sourceGroup000001_source : RVFileSource (0) { media { string movie = "b.mov" } } + +connections : connection (1) +{ + evaluation + { + string lhs = [ "sourceGroup000000" + "sourceGroup000001" ] + string rhs = [ "mySequence" + "mySequence" ] + } +} + +mySequence : RVSequenceGroup (0) +{ + ui + { + string name = "GUI Name of My Sequence" + } +} + +mySequence_sequence : RVSequence (0) +{ + edl + { + int frame = [ 1 11 21 31 41 ] + int source = [ 0 1 0 1 0 ] + int in = [ 1 1 11 11 0 ] + int out = [ 10 10 20 20 0 ] + } + + mode + { + int autoEDL = 0 + } +} +``` +The source property indexes the inputs to the sequence node. So index 0 refers to sourceGroup000000 and index 1 refers to sourceGroup000001. This EDL has four edits which are played sequentially as follows: + +1. a.mov, frames 1-10 +2. b.mov, frames 1-10 +3. a.mov, frames 11-20 +4. b.mov, frames 11-20 + +You can think of the properties in the sequence as forming a transposed matrix in which the properties are columns and edits are rows as in [6.1](rv-reference-manual-chapter-six.md#edl-as-matrix). Note that there are only 4 edits even though there are 5 rows in the matrix. The last edit is really just a boundary condition: it indicates how RV should handle frames past the end of the EDL. To be well formed, an RV EDL needs to include this.Note that the in frame and out frame may be equal to implement a “held” frame. + +| | global start frame | source | in | out | +| --- | --- | --- | --- | --- | +| edit #1 | 1 | a.mov | 1 | 10 | +| edit #2 | 11 | b.mov | 1 | 10 | +| edit #3 | 21 | a.mov | 11 | 20 | +| edit #4 | 31 | b.mov | 11 | 20 | +| past end | 41 | a.mov | 0 | 0 | + +Table 6.1:EDL as Matrix + +#### 6.7.2 RVLayoutGroup and RVStackGroup + +The stack and layout groups can be made in a similar manner to the above. The important thing to remember is the inputs for all of these must be specified in the connections object of the file. Each of these view types uses the input ordering; in the case of the stack it determines what's on top and in the case of the layout it determines how automatic layout will be ordered. + +#### 6.7.3 RVOverlay + +Burned in metadata can be useful when creating session files. Shot status, artist, name, sequence, and other static information can be rendered on top of the source image directly by RV's render. Figure [6.1](#metadata-rendered-by-rvoverlay-node-from-session-file) shows an example of metadata rendered by the RVOverlay node. + +![11_ase_overlay_shot.png](../../images/rv-reference-manual-11-rv-cx-ase-overlay-shot-010.png) + +Figure 6.1:Metadata Rendered By RVOverlay Node From Session File + +Each RVSourceGroup can have an RVOverlay node. The RVOverlay node is used for matte rendering by user interface, but it can do much more than that. The RVOverlay node currently supports drawing arbitrary filled rectangles and text in addition to the mattes. The text and filled rectangle are currently limited to static shapes and text. Text and rectangles rendered in this fashion are considered part of the image by RV. If you pass a session file with an active RVOverlay node to rvio it will render the overlay the same way RV would. This is completely independent of any rvio overlay scripts which use a different mechanism to generate overlay drawings and text.Figure [6.2](#rvoverlay-node-example) shows an example which draws three colored boxes with text starting at each box's origin. + +![12_overlay_example.png](../../images/rv-reference-manual-12-rv-cx-overlay-example-11.png) + +Figure 6.2:RVOverlay Node Example + +The session file used to create the example contains a movieproc source (white 720x480 image) with the overlay rendered on top of it. Note that the coordinates are normalized screen coordinates relative to the source image: + +``` + GTOa (3) + +sourceGroup1_source : RVFileSource (1) +{ + media + { + string movie = "solid,red=1.0,green=1.0,blue=1.0,start=1,end=1,width=720,height=480.movieproc" + } +} + +sourceGroup1_overlay : RVOverlay (1) +{ + overlay + { + int show = 1 + } + "rect:red" + { + float width = 0.3 + float height = 0.3 + float[4] color = [ [ 1.0 0.1 0.1 0.4 ] ] + float[2] position = [ [ 0.1 0.1 ] ] + } + "rect:green" + { + float width = 0.6 + float height = 0.2 + float[4] color = [ [ 0.1 1.0 0.1 0.4 ] ] + float[2] position = [ [ -0.2 -0.3 ] ] + } + "rect:blue" + { + float width = 0.2 + float height = 0.4 + float[4] color = [ [ 0.1 0.1 1.0 0.4 ] ] + float[2] position = [ [ -0.5 -0.1 ] ] + } + "text:red" + { + float[2] position = [ [ 0.1 0.1 ] ] + float[4] color = [ [ 0 0 0 1 ] ] + float spacing = 0.8 + float size = 0.005 + float scale = 1 + float rotation = 0 + string font = "" + string text = "red" + int debug = 0 + } + "text:green" + { + float[2] position = [ [ -0.2 -0.3 ] ] + float[4] color = [ [ 0 0 0 1 ] ] + float spacing = 0.8 + float size = 0.005 + float scale = 1 + float rotation = 0 + string font = "" + string text = "green" + int debug = 0 + } + "text:blue" + { + float[2] position = [ [ -0.5 -0.1 ] ] + float[4] color = [ [ 0 0 0 1 ] ] + float spacing = 0.8 + float size = 0.005 + float scale = 1 + float rotation = 0 + string font = "" + string text = "blue" + int debug = 0 + } +} + +``` + +Components in the RVOverlay which have names starting with “rect:” are used to render filled rectangles. Components starting with “text:” are used for text. The format is similar to that used by the RVPaint node, but the result is rendered for all frames of the source. The reference manual contains complete information about the RVOverlay node's properties and how the control rendering. + +### 6.8 Limitations on Number of Open Files + + +RV does not impose any artificial limits on the number of source which can be in an RV session file. However, the use of some file formats, namely Quicktime .mov, .avi, and .mp4, require that the file remain open while RV is running.Each operating system (and even shell on Unix systems) has different limits on the number of open files a process is allowed to have. For example on Linux the default is 1024 files. This means that you cannot open more than 1000 or so movie files without changing the default. RV checks the limit on startup and sets it to the maximum allowed by the system.There are a number of operating system and shell dependent ways to change limits. Your facility may also have limits imposed by the IT department for accounting reasons. + +### 6.9 What's the Best Way to Write a .rv (GTO) File? + + +GTO comes in three types: text (UTF8 or ASCII), binary, and compressed binary. RV can read all three types. RV normally writes text files unless an **RVImageSource** is present in the session (because an image was sent to it from another process instead of a file). In that case it will write a compressed binary GTO to save space on disk.If you think you might want to generate binary files in addition to text files you can do so using the GTO API in C++ or python. However, the text version is simple enough to write using only regular I/O APIs in any language. We recommend you write out .rv session files from RV and look at them in an editor to generate templates of the portions that are important to you. You can copy and paste parts of session files into source code as strings or even shell scripts as templates with variable substitution. diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-sixteen.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-sixteen.md new file mode 100644 index 000000000..bd1fb6ddb --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-sixteen.md @@ -0,0 +1,676 @@ +# Chapter 16 - Node Reference + +This chapter has a section for each type of node in RV's image processing graph. The properties and descriptions listed here are the default properties. Any top level node that can be seen in the session manager can have the “name” property of the “ui” component set in order to control how the node is listed. + +### RVCache + +The RVCache node has no external properties. + +### RVCacheLUT and RVLookLUT + + +The RVCacheLUT is applied in software before the image is cached and before any software resolution and bit depth changes. The RVLookLUT is applied just before the display LUT but is per-source. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| lut.lut | float | div 3 | Contains either a 3D or a channel look LUT | +| lut.prelut | float | div 3 | Contains a channel pre-LUT | +| lut.inMatrix | float | 16 | Input color matrix | +| lut.outMatrix | float | 16 | Output color matrix | +| lut.scale | float | 1 | LUT output scale factor | +| lut.offset | float | 1 | LUT output offset | +| lut.file | string | 1 | Path of LUT file to read when RV session is loaded | +| lut.size | int | 1 or 3 | With 1 size value, the look LUT is a channel LUT of the specified size, if there are 3 values the look LUT is a 3D LUT with the dimensions indicated | +| lut.active | int | 1 | If non-0 the LUT is active | +| lut:output.size | int | 1 or 3 | The resampled LUT output size | +| lut:output.lut | float or half | div 3 | The resampled output LUT | +| lut:output.prelut | float or half | div 3 | The resampled output pre-LUT | + +### RVCDL + + +This node can be used to load CDL properties from CCC, CC, and CDL files on disk. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| node.active | int | 1 | If non-0 the CDL is active. A value of 0 turns off the node. | +| node.colorspace | string | 1 | Can be "rec709", "aces", or "aceslog" and the default is "rec709". | +| node.file | string | 1 | Path of CCC, CC, or CDL file from which to read properties. | +| node.slope | float[3] | 1 | Color Decision List per-channel slope control | +| node.offset | float[3] | 1 | Color Decision List per-channel offset control | +| node.power | float[3] | 1 | Color Decision List per-channel power control | +| node.saturation | float | 1 | Color Decision List saturation control | +| node.noClamp | int | 1 | Set to 1 to remove clamping from CDL equations | + +### RVChannelMap + + +This node can be used to remap channels that may have been labeled incorrectly. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| format.channels | string | >= 0 | An array of channel names. If the property is empty the image will pass though the node unchanged. Otherwise, only those channels appearing in the property array will be output. The channel order will be the same as the order in the property. | + +### RVColor + + +The color node has a large number of color controls. This node is usually evaluated on the GPU, except when normalize is 1. The CDL is applied after linearization and linear color changes. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| color.normalize | int | 1 | Non-0 means to normalize the incoming pixels to [0,1] | +| color.invert | int | 1 | If non-0, invert the image color using the inversion matrix (See User's Manual) | +| color.gamma | float[3] | 1 | Apply a gamma. The default is [1.0, 1.0, 1.0]. The three values are applied to R G and B channels independently. | +| color.offset | float[3] | 1 | Color bias added to incoming color channels. Default = 0 (not bias). Each component is applied to R G B independently. | +| color.scale | float[3] | 1 | Scales each channel by the respective float value. | +| color.exposure | float[3] | 1 | Relative exposure in stops. Default = [0, 0, 0], See user's manual for more information on this. Each component is applied to R G and B independently. | +| color.contrast | float[3] | 1 | Contrast applied per channel (see User's Manual) | +| color.saturation | float | 1 | Relative saturation (see User's Manual) | +| color.hue | float | 1 | Hue rotation in radians (see User's Manual) | +| color.active | int | 1 | If 0, do not apply any color transforms. Turns off the node. | +| CDL.slope | float[3] | 1 | Color Decision List per-channel slope control | +| CDL.offset | float[3] | 1 | Color Decision List per-channel offset control | +| CDL.power | float[3] | 1 | Color Decision List per-channel power control | +| CDL.saturation | float | 1 | Color Decision List saturation control | +| CDL.noClamp | int | 1 | Set to 1 to remove clamping from CDL equations | +| luminanceLUT.lut | float | div 3 | Luminance LUT to be applied to incoming image. Contains R G B triples one after another. The LUT resolution | +| luminanceLUT.max | float | 1 | A scale on the output of the Luminance LUT | +| luminanceLUT.active | int | 1 | If non-0, luminance LUT should be applied | +| luminanceLUT:output.size | int | 1 | Output Luminance lut size | +| luminanceLUT:output.lut | float | div 3 | Output resampled luminance LUT | + +### RVDispTransform2D + + +This node is used to do any scaling or translating of the corresponding view group. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| transform.translate | float[2] | 1 | Viewing translation | +| transform.scale | float[2] | 1 | Viewing scale | + +### RVDisplayColor + + +This node is used by default by any display group as part of its color management pipeline. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| color.channelOrder | string | 1 | A four character string containing any of the characters [RGBA10]. The order allows permutation of the normal R G B and A channels as well as filling any channel with 1 or 0. | +| color.channelFlood | int | 1 | If 0 pass the channels through as they are. When the value is 1, 2, 3, or 4, the R G B or A channels are used to flood the R G and B channels. When the value is 5, the luminance of each pixel is computed and displayed as a gray scale image. | +| color.gamma | float | 1 | A single gamma value applied to all channels, default = 1.0 | +| color.sRGB | int | 1 | If non-0 a linear to sRGB space transform occurs | +| color.Rec709 | int | 1 | If non-0 the Rec709 transfer function is applied | +| color.brightness | float | 1 | In relative stops, the final pixel values are brightened or dimmed according to this value. Occurs after all color space transforms. | +| color.outOfRange | int | 1 | If non-0 pass pixels through an out of range filter. Channel values in the (0,1] are set to 0.5, channel values [-inf,0] are set to 0 and channel values (1,inf] are set to 1.0. | +| color.active | int | 1 | If 0 deactivate the display node | +| lut.lut | float | div 3 | Contains either a 3D or a channel display LUT | +| lut.prelut | float | div 3 | Contains a channel pre-LUT | +| lut.scale | float | 1 | LUT output scale factor | +| lut.offset | float | 1 | LUT output offset | +| lut.inMatrix | float | 16 | Input color matrix | +| lut.outMatrix | float | 16 | Output color matrix | +| lut.file | string | 1 | Path of LUT file to read when RV session is loaded | +| lut.size | int | 1 or 3 | With 1 size value, the display LUT is a channel LUT of the specified size, if there are 3 values the display LUT is a 3D LUT with the dimensions indicated | +| lut.active | int | 1 | If non-0 the display LUT is active | +| lut:output.size | int | 1 or 3 | The resampled LUT output size | +| lut:output.lut | float or half | div 3 | The resampled output LUT | +| lut:output.prelut | float or half | div 3 | The resampled output pre-LUT | + +### RVDisplayGroup and RVOutputGroup + + +The display group provides per device display conditioning. The output group is the analogous node group for RVIO. The display groups are never saved in the session, but there is only one output group and it is saved for RVIO. There are no user external properties at this time. + +### RVDisplayStereo + + +This node governs how to handle stereo playback including controlling the placement of stereo sources. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| rightTransform.flip | int | 1 | Flip the right eye top to bottom. | +| rightTransform.flop | int | 1 | Flop the right eye left to right. | +| rightTransform.rotate | float | 1 | Rotation of right eye in degrees. | +| rightTransform.translate | float[2] | 1 | Translation offset in X and Y for the right eye. | +| stereo.relativeOffset | float | 1 | Relative stereo offset for both eyes. | +| stereo.rightOffset | float | 1 | Stereo offset for right eye only. | +| stereo.swap | int | 1 | If set to 1 treat left eye as right and right eye as left. | +| stereo.type | string | 1 | Stereo mode in use. For example: left, right, pair, mirror, scanline, anaglyph, checker... (default is off) | + +### RVFileSource + + +The source node controls file I/O and organize the source media into layers (in the RV sense). It has basic controls needed to mix the layers together. + +| Name | Type | Size | Description | +| --- | --- | --- | --- | +| media.movie | string | > 1 | The movie, image, audio files and image sequence names. Each name is a layer in the source.There is typically at least one value in this property | +| group.fps | float | 1 | Overrides the fps found in any movie or image file or if none is found overrides the default fps of 24. | +| group.volume | float | 1 | Relative volume. This can be any positive number or 0. | +| group.audioOffset | float | 1 | Audio offset in seconds. All audio layers will be offset. | +| group.rangeOffset | int | 1 | Shifts the start and end frame numbers of all image media in the source. | +| group.rangeStart | int | 1 | Resets the start frame of all image media to given value. This is an optional property. It must be created to be set and removed to unset. | +| group.balance | float | 1 | Range of [-1,1]. A value of 0 means the audio volume is the same for both the left and right channels. | +| group.noMovieAudio | int | 1 | Do not use audio tracks in movies files | +| cut.in | int | 1 | The preferred start frame of the sequence/movie file | +| cut.out | int | 1 | The preferred end frame of the sequence/movie file | +| request.readAllChannels | int | 1 | If the value is 1 and the image format can read multiple channels, it is requested to read all channels in the current image layer and view. | +| request.imageComponent | string | 2, 3, or 4 | This array is of the form: type, view, [layer[, channel]]. The type describes what is defined in the remainder of the array. The type may be one of ”view”, ”layer”, or ”channel”. The 2nd element of the array must be defined and is the value of the view. If there are 3 elements defined then the 3rd is the layer name. If there are 4 elements defined then the 4th is the channel name. | +| request.stereoViews | string | 0 or 2 | If there are values in this property, they will be passed to the image reader when in stereo viewing mode as requested view names for the left and right eyes. | +| attributes.key | string, int, or float | 1 | This optional container of properties will get automatically included in the metadata associated with the source. The key can be any string and will be displayed as the metadata item name when displayed in the Image Info. The value of the property will be displayed as the value of the metadata. | + +### RVFolderGroup + + +The folder group contains either a SwitchGroup or LayoutGroup which determines how it is displayed. + +| Name | Type | Size | Description | +| --- | --- | --- | --- | +| ui.name | string | 1 | This is a user specified name which appears in the user interface. | +| mode.viewType | string | 1 | Either “switch” or “layout”. Determines how the folder is displayed. | + +### RVFormat + + +This node is used to alter geometry or color depth of an image source. It is part of an RVSourceGroup. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| geometry.xfit | int | 1 | Forces the resolution to a specific width | +| geometry.yfit | int | 1 | Forces the resolution to a specific height | +| geometry.xresize | int | 1 | Forces the resolution to a specific width | +| geometry.yresize | int | 1 | Forces the resolution to a specific height | +| geometry.scale | float | 1 | Multiplier on incoming resolution. E.g., 0.5 when applied to 2048x1556 results in a 1024x768 image. | +| geometry.resampleMethod | string | 1 | Method to use when resampling. The possible values are area, cubic, and linear, | +| crop.active | int | 1 | If non-0 cropping is active | +| crop.xmin | int | 1 | Minimum X value of crop in pixel space | +| crop.ymin | int | 1 | Minimum Y value of crop in pixel space | +| crop.xmax | int | 1 | Maximum X value of crop in pixel space | +| crop.ymax | int | 1 | Maximum Y value of crop in pixel space | +| uncrop.active | int | 1 | In non-0 uncrop region is used | +| uncrop.x | int | 1 | X offset of input image into uncropped image space | +| uncrop.y | int | 1 | Y offset of input image into uncropped image space | +| uncrop.width | int | 1 | Width of uncropped image space | +| uncrop.height | int | 1 | Height of uncropped image space | +| color.maxBitDepth | int | 1 | One of 8, 16, or 32 indicating the maximum allowed bit depth (for either float or integer pixels) | +| color.allowFloatingPoint | int | 1 | If non-0 floating point images will be allowed on the GPU otherwise, the image will be converted to integer of the same bit depth (or the maximum bit depth). | +| | | | | + +### RVImageSource + + +The RV image source is subset of what RV can handle from an external file (basically just EXR). Image sources can have multiple views each of which have multiple layers. However, all views must have the same layers. Image sources cannot have layers within layers, orphaned channels, empty views, missing views, or other weirdnesses that EXR can have. + +| Name | Type | Size | Description | +| --- | --- | --- | --- | +| media.movie | string | > 1 | The movie, image, audio files and image sequence names. Each name is a layer in the source.There is typically at least one value in this property. | +| media.name | string | 1 | The name for this image. | +| cut.in | int | 1 | The preferred start frame of the sequence/movie file. | +| cut.out | int | 1 | The preferred end frame of the sequence/movie file. | +| image.channels | string | 1 | String representing the channels in the image. | +| image.layers | string | > 1 | List of strings representing the layers in the image. | +| image.views | string | > 1 | List of strings representing the views in the image. | +| image.defaultLayer | string | 1 | String representing the layer from image.layers that should be treated as default layer. | +| image.defaultView | string | 1 | String representing the view from image.views that should be treated as default view. | +| image.start | int | 1 | First frame of the source. | +| image.end | int | 1 | Last frame of the source. | +| image.inc | int | 1 | Number of frames to step by. | +| image.fps | float | 1 | Frame rate of source in float ratio of frames per second. | +| image.pixelAspect | float | 1 | Image aspect ratio as a float of width over height. | +| image.uncropHeight | int | 1 | Height of uncropped image space. | +| image.uncropWidth | int | 1 | Width of uncropped image space. | +| image.uncropX | int | 1 | X offset of image into uncropped image space. | +| image.uncropY | int | 1 | Y offset of image into uncropped image space. | +| image.width | int | 1 | Image width in integer pixels. | +| image.height | int | 1 | Image height in integer pixels. | +| request.imageChannelSelection | string | Any | Any values are considered image channel names. These are passed to the image readers with the request that only these layers be read from the image pixels. | +| request.imageComponent | string | 2, 3, or 4 | This array is of the form: type, view, [layer[, channel]]. The type describes what is defined in the remainder of the array. The type may be one of ”view”, ”layer”, or ”channel”. The 2nd element of the array must be defined and is the value of the view. If there are 3 elements defined then the 3rd is the layer name. If there are 4 elements defined then the 4th is the channel name. | +| request.stereoViews | string | 0 or 2 | If there are values in this property, they will be passed to the image reader when in stereo viewing mode as requested view names for the left and right eyes. | +| attributes.key | string, int, or float | 1 | This optional container of properties will get automatically included in the metadata associated with the source. The key can be any string and will be displayed as the metadata item name when displayed in the Image Info. The value of the property will be displayed as the value of the metadata. | + +### RVLayoutGroup + + +The source group contains a single chain of nodes the leaf of which is an RVFileSource or RVImageSource. It has a single property. + +| Name | Type | Size | Description | +| --- | --- | --- | --- | +| ui.name | string | 1 | This is a user specified name which appears in the user interface. | +| layout.mode | string | 1 | The string mode that dictates the way items are layed out. Possible values are: packed, packed2, row, column, and grid (default is packed). | +| layout.spacing | float | 1 | Scale the items in the layout. Legal values are between 0.0 and 1.0. | +| layout.gridColumns | int | 1 | When in grid mode constrain grid to this many columns. If this set to 0, then the number of columns will be determined by gridRows. If both are 0, then both will be automatically calculated. | +| layout.gridRows | int | 1 | When in grid mode constrain grid to this many rows. If this is set to 0, then the number of rows will be determined by gridColumns. This value is ignored when gridColumns is non-zero. | +| timing.retimeInputs | int | 1 | Retime all inputs to the output fps if 1 otherwise play back their frames one at a time at the output fps. | + +### RVLensWarp + + +This node handles the pixel aspect ratio of a source group. The lens warp node can also be used to perform radial and/or tangential distortion on a frame. It implements the [Brown's distortion model](http://en.wikipedia.org/wiki/Distortion_%28optics%29) (similar to [that adopted by OpenCV](http://opencv.willowgarage.com/documentation/camera_calibration_and_3d_reconstruction.html) or Adobe Lens Camera Profile model) and 3DE4's Anamorphic Degree6 model. This node can be used to perform operations like lens distortion or artistic lens warp effects. + +| Name | Type | Size | Description | +| --- | --- | --- | --- | +| warp.pixelAspectRatio | float | 1 | If non-0 set the pixel aspect ratio. Otherwise use the pixel aspect ratio reported by the incoming image. (default 0, ignored) | +| warp.model | string | | Lens model: choices are “brown”, “opencv”, “pfbarrel”, “adobe”, “3de4_anamorphic_degree_6, “rv4.0.10”. | +| warp.k1 | float | 1 | Radial coefficient for r^2 (default 0.0)Applicable to “brown”, “opencv”, “pfbarrel”, “adobe”. | +| warp.k2 | float | 1 | Radial coefficient for r^4 (default 0.0)Applicable to “brown”, “opencv”, “pfbarrel”, “adobe”. | +| warp.k3 | float | 1 | Radial coefficient for r^6 (default 0.0)Applicable to “brown”, “opencv”, “adobe”. | +| warp.p1 | float | 1 | First tangential coefficient (default 0.0)Applicable to “brown”, “opencv”, “adobe”. | +| warp.p2 | float | 1 | Second tangential coefficient (default 0.0)Applicable to “brown”, “opencv”, “adobe”. | +| warp.cx02 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cy02 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cx22 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cy22 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cx04 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cy04 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cx24 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cy24 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cx44 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cy44 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cx06 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cy06 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cx26 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cy26 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cx46 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cy46 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cx66 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.cy66 | float | 1 | Applicable to “3de4_anamorphic_degree_6”. (default 0.0) | +| warp.center | float[2] | 1 | Position of distortion center in normalized values [0...1] (default [0.5 0.5]. Applicable to all models. | +| warp.offset | float[2] | 1 | Offset from distortion center in normalized values [0...1.0] (default [0.0 0.0]). Applicable to all models. | +| warp.fx | float | 1 | Normalized FocalLength in X (default 1.0).Applicable to “brown”, “opencv”, “adobe”, “3de4_anamorphic_degree_6”. | +| warp.fy | float | 1 | Normalized FocalLength in Y (default 1.0).Applicable to “brown”, “opencv”, “adobe”, “3de4_anamorphic_degree_6”. | +| warp.cropRatioX | float | 1 | Crop ratio of fovX (default 1.0). Applicable to all models. | +| warp.cropRatioY | float | 1 | Crop ratio of fovY (default 1.0). Applicable to all models. | +| node.active | int | 1 | If 0, do not apply any warp/pixel aspect ratio transform. Turns off the node. (default 1) | + +Example use case: Using OpenCV to determine lens distort parameters for RVLensWarp node based on GoPro footage. First capture some footage of a checkboard with your GoPro. Then you can use OpenCV camera calibration approach on this footage to solve for k1,k2,k3,p1 and p2. In OpenCV these numbers are reported back as follows. For example our 1920x1440 Hero3 Black GoPro solve returned: + +``` + fx=829.122253 0.000000 cx=969.551819 + 0.000000 fy=829.122253 cy=687.480774 + 0.000000 0.000000 1.000000 + k1=-0.198361 k2=0.028252 p1=0.000092 p2=-0.000073 +``` +The OpenCV camera calibration solve output numbers are then translated/normalized to the RVLensWarpode property values as follows: + +``` + warp.model = "opencv" + warp.k1 = k1 + warp.k2 = k2 + warp.p1 = p1 + warp.p2 = p2 + warp.center = [cx/1920 cy/1440] + warp.fx = fx/1920 + warp.fy = fy/1920 +``` +e.g. mu code: + +``` + set("#RVLensWarp.warp.model", "opencv"); + set("#RVLensWarp.warp.k1", -0.198361); + set("#RVLensWarp.warp.k2", 0.028252); + set("#RVLensWarp.warp.p1", 0.00092); + set("#RVLensWarp.warp.p2", -0.00073); + setFloatProperty("#RVLensWarp.warp.offset", float[]{0.505, 0.4774}, true); + set("#RVLensWarp.warp.fx", 0.43185); + set("#RVLensWarp.warp.fy", 0.43185); +``` +Example use case: Using Adobe LCP (Lens Camera Profile) distort parameters for RVLensWarp node. Adobe LCP files can be located in '/Library/Application Support/Adobe/CameraRaw/LensProfiles/1.0' under OSX. Adobe LCP file parameters maps to the RVLensWarp node properties as follows: + +``` + warp.model = "adobe" + warp.k1 = stCamera:RadialDistortParam1 + warp.k2 = stCamera:RadialDistortParam2 + warp.k3 = stCamera:RadialDistortParam3 + warp.p1 = stCamera:TangentialDistortParam1 + warp.p2 = stCamera:TangentialDistortParam2 + warp.center = [stCamera:ImageXCenter stCamera:ImageYCenter] + warp.fx = stCamera:FocalLengthX + warp.fy = stCamera:FocalLengthY +``` + +### RVLinearize + + +The linearize node has a large number of color controls. The CDL is applied before linearization occurs. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| color.alphaType | int | 1 | By default (0), uses the alpha type reported by the incoming image. Otherwise, 1 means the alpha is premultiplied, 0 means the incoming alpha is unpremultiplied. | +| color.YUV | int | 1 | If the value is non-0, convert the incoming pixels from YUV space to linear space. | +| color.logtype | int | 1 | The default (0), means no log to linear transform, 1 uses the cineon transform (see cineon.whiteCodeValue and cineon.blackCodeValue below), 2 means use the Viper camera log to linear transform, and 3 means use LogC log to linear transform. | +| color.sRGB2linear | int | 1 | If the value is non-0, convert the incoming pixels from sRGB space to linear space. | +| color.Rec709ToLinear | int | 1 | If the value is non-0, convert the incoming pixels using the inverse of the Rec709 transfer function. | +| color.fileGamma | float | 1 | Apply a gamma to linearize the incoming image. The default is 1.0. | +| color.active | int | 1 | If 0, do not apply any color transforms. Turns off the node. | +| color.ignoreChromaticities | int | 1 | If non-0, ignore any non-Rec 709 chromaticities reported by the incoming image. | +| CDL.slope | float[3] | 1 | Color Decision List per-channel slope control. | +| CDL.offset | float[3] | 1 | Color Decision List per-channel offset control. | +| CDL.power | float[3] | 1 | Color Decision List per-channel power control. | +| CDL.saturation | float | 1 | Color Decision List saturation control. | +| CDL.noClamp | int | 1 | Set to 1 to remove clamping from CDL equations. | +| CDL.active | int | 1 | If non-0 the CDL is active. | +| lut.lut | float | div 3 | Contains either a 3D or a channel file LUT. | +| lut.prelut | float | div 3 | Contains a channel pre-LUT. | +| lut.inMatrix | float | 16 | Input color matrix. | +| lut.outMatrix | float | 16 | Output color matrix. | +| lut.scale | float | 1 | LUT output scale factor. | +| lut.offset | float | 1 | LUT output offset. | +| lut.file | string | 1 | Path of LUT file to read when RV session is loaded. | +| lut.size | int | 1 or 3 | With 1 size value, the file LUT is a channel LUT of the specified size, if there are 3 values the file LUT is a 3D LUT with the dimensions indicated. | +| lut.active | int | 1 | If non-0 the file LUT is active. | +| lut:output.size | int | 1 or 3 | The resampled LUT output size. | +| lut:output.lut | float or half | div 3 | The resampled output LUT. | + +### OCIO (OpenColorIO), OCIOFile, OCIOLook, and OCIODisplay + + +OpenColorIO nodes can be used in place of existing RV LUT pipelines. Properties in RVColorPipelineGroup, RVLinearizePipelineGroup, RVLookPipelineGroup, and RVDisplayPipelineGroup determine whether or not the OCIO nodes are used. All OCIO nodes have the same properties and function, but their location in the color pipeline is determined by their type. The exception is the generic OCIO node which can be created by the user and used in any context.NOTE: THIS IS INCOMPLETE – [SEE ACCOMPANYING OCIO INTEGRATION DOCUMENT](../rv-opencolorio-integrations.md) + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| ocio.lut | float | div 3 | Contains a 3D LUT, size determined by ocio.lut3DSize | +| lut.prelut | float | div 3 | Currently unused | +| ocio.active | int | 1 | Non-0 means node is active | +| ocio.lut3DSize | int | 1 | 3D LUT size of all dimensions (default is 32) | +| ocio.inSpace | string | 1 | Name of OCIO input colorspace | +| ocio_context. *name* | string | 1 | Name/Value pairs for OCIO context | + +### RVOverlay + + +Overlay nodes can be used with any source. They can be used to draw arbitrary rectangles and text over the source but beneath any annotations. Overlay nodes can hold any number of 3 types of components: **rect** components describe a rectangle to be rendered, **text** components describe a string (or an array of strings, one per frame) to be rendered, and **window** components describe a matted region to be indicated either by coloring the region outside the window, or by outlining it. The coordiates of the corners of the window may be animated by specifying one number per frame.In the below the “ **id** ” in the component name can be any string, but must be different for each component of the same type. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| overlay.nextRectId | int | 1 | (unused) | +| overlay.nextTextId | int | 1 | (unused) | +| overlay.show | int | 1 | If 1 display any rectangles/text/window entries. If 0 do not. | +| matte.show | int | 1 | If 1 display the source specific matte, not the global | +| matte.aspect | float | 1 | Aspect ratio of the source's matte | +| matte.opacity | float | 1 | Opacity of the source's matte | +| matte.heightVisible | float | 1 | Fraction of the source height that is still visible from the matte. | +| matte.centerPoint | float[2] | 1 | The center of the matte stored as X, Y in normalized coordinates. | +| rect: *id* .color | float[4] | 1 | The color of the rectangle | +| rect: *id* .width | float | 1 | The width of the rectangle in the normalized coordinate system | +| rect: *id* .height | float | 1 | The height of the rectangle in the normalized coordinate system | +| rect: *id* .position | float[2] | 1 | Location of the rectangle in the normalized coordinate system | +| rect: *id* .active | int | 1 | If 0, rect will not be rendered | +| rect: *id* .eye | int | 1 | If absent, or set to 2, the rectangle will be rendered in both stereo eyes. If set to 0 or 1, only in the corresponding eye. | +| text: *id* .pixelScale | float[2] | 1 | X and Y scaling factors for position, IE expected source resolution, if present and non-zero, position is expected in “pixels”. | +| text: *id* .position | float[2] | 1 | Location of the text (coordinate are normalized unless pixelScale is set, in which case they are in “pixels”) | +| text: *id* .color | float[4] | 1 | The color of the text | +| text: *id* .spacing | float | 1 | The spacing of the text | +| text: *id* .size | float | 1 | The size of the text | +| text: *id* .scale | float | 1 | The scale of the text | +| text: *id* .rotation | float | 1 | (unused) | +| text: *id* .font | string | 1 | The path to the .ttf (TrueType) font to use (Default is Luxi Serif) | +| text: *id* .text | string | N | Text to be rendered, if multi-valued there should be one string per frame in the expected range. | +| text: *id* .origin | string | 1 | The origin of the text box. The position property will store the location of the origin, but the origin can be on any corner of the text box or centered in between. The valid possible values for origin are top-left, top-center, top-right, center-left, center-center, center-right, bottom-left, bottom-center, bottom-right, and the empty string (which is the default for backwards compatibility). | +| text: *id* .eye | int | 1 | If absent, or set to 2, the rectangle will be rendered in both stereo eyes. If set to 0 or 1, only in the corresponding eye. | +| text: *id* .active | int | 1 | If active is 0, the text item will not be rendered | +| text: *id* .firstFrame | int | 1 | If the “text” property is multi-valued, this property indicates the frame number corresponding to the first text value. | +| text: *id* .debug | int | 1 | (unused) | +| window: *id* .eye | int | 1 | If absent, or set to 2, the rectangle will be rendered in both stereo eyes. If set to 0 or 1, only in the corresponding eye. | +| window: *id* .antialias | int | 1 | If 1, outline/window edge drawing will be antialiased. Default 0. | +| window: *id* .windowActive | int | 1 | If windowActive is 0, the window “matting” will not be rendered | +| window: *id* .outlineActive | int | 1 | If outlineActive is 0, the window outline will not be rendered | +| window: *id* .outlineWidth | float | 1 | Assuming antialias = 1, nominal width in image-space pixels of the outline (and the degree of blurriness of the matte edge). Default 3.0 | +| window: *id* .outlineBrush | string | 1 | Assuming antialias = 1, brush used to stroke the outline (choices are "gauss" or "solid"). Default is gauss. | +| window: *id* .windowColor | float[4] | 1 | The color of the window “matting”. | +| window: *id* .outlineColor | float[4] | 1 | The color of the window outline. | +| window: *id* .imageAspect | float | 1 | The expected imageAspect of the media. If imageAspect is present and non-zero, normalized window coordinates are expected. | +| window: *id* .pixelScale | float[2] | 1 | X and Y scaling factors for window coordinates, IE expected source resolution. Used to normalize window coords in “pixels”. For pixelScale to take effect, imageAspect must be missing or 0. | +| window: *id* .firstFrame | int | 1 | If any of the window coord properties is multi-valued, this property indicates the frame number corresponding to the first coord value. | +| window: *id.windowULx* | float | N | Upper left window corner (x coord). | +| window: *id.windowULy* | float | N | Upper left window corner (y coord). | +| window: *id.windowLLx* | float | N | Lower left window corner (x coord). | +| window: *id.windowLLy* | float | N | Lower left window corner (y coord). | +| window: *id.windowURx* | float | N | Upper right window corner (x coord). | +| window: *id.windowURy* | float | N | Upper right window corner (y coord). | +| window: *id.windowLRx* | float | N | Lower right window corner (x coord). | +| window: *id.windowLRy* | float | N | Lower right window corner (y coord). | + +### RVPaint + + +Paint nodes are used primarily to store per frame annotations. Below *id* is the value of nextID at the time the paint command property was created, *frame* is the frame on which the annotation will appear, *user* is the username of the user who created the property. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| paint.nextId | int | 1 | A counter used by the annotation mode to uniquely tag annotation pen strokes and text. | +| paint.nextAnnotationId | int | 1 | (unused) | +| paint.show | int | 1 | If 1 display any paint strokes and text entries. If 0 do not. | +| paint.exclude | string | N | (unused) | +| paint.include | string | N | (unused) | +| pen: *id* : *frame* : *user* .color | float[4] | 1 | The color of the pen stroke | +| pen: *id* : *frame* : *user* .width | float | 1 | The width of the pen stroke | +| pen: *id* : *frame* : *user* .brush | string | 1 | Brush style of “gauss” or “circle” for soft or hard lines respectively | +| pen: *id* : *frame* : *user* .points | float[2] | N | Points of the stroke in the normalized coordinate system | +| pen: *id* : *frame* : *user* .debug | int | 1 | If 1 show multicolored bounding lines around the stroke. | +| pen: *id* : *frame* : *user* .join | int | 1 | The joining style of the stroke:NoJoin = 0; BevelJoin = 1; MiterJoin = 2; RoundJoin = 3; | +| pen: *id* : *frame* : *user* .cap | int | 1 | The cap style of the stroke:NoCap = 0; SquareCap = 1; RoundCap = 2; | +| pen: *id* : *frame* : *user* .splat | int | 1 | | +| pen: *id* : *frame* : *user* .mode | int | 1 | Drawing mode of the stroke (Default if missing is 0):RenderOverMode = 0; RenderEraseMode = 1; | +| text: *id* : *frame* : *user* .position | float[2] | 1 | Location of the text in the normalized coordinate system | +| text: *id* : *frame* : *user* .color | float[4] | 1 | The color of the text | +| text: *id* : *frame* : *user* .spacing | float | 1 | The spacing of the text | +| text: *id* : *frame* : *user* .size | float | 1 | The size of the text | +| text: *id* : *frame* : *user* .scale | float | 1 | The scale of the text | +| text: *id* : *frame* : *user* .rotation | float | 1 | (unused) | +| text: *id* : *frame* : *user* .font | string | 1 | The path to the .ttf (TrueType) font to use (Default is Luxi Serif) | +| text: *id* : *frame* : *user* .text | string | 1 | Content of the text | +| text: *id* : *frame* : *user* .origin | string | 1 | The origin of the text box. The position property will store the location of the origin, but the origin can be on any corner of the text box or centered in between. The valid possible values for origin are top-left, top-center, top-right, center-left, center-center, center-right, bottom-left, bottom-center, bottom-right, and the empty string (which is the default for backwards compatibility). | +| text: *id* : *frame* : *user* .debug | int | 1 | (unused) | + +### RVPrimaryConvert + + +The primary convert node can be used to perform primary colorspace conversion with illuminant adaptation on a frame that has been linearized. The input and output colorspace primaries are specified in terms of input and output chromaticities for red, green, blue and white points. Illuminant adaptation is implemented using the Bradford transform where the input and output illuminant are specified in terms of their white points. Illuminant adaptation is optional. Default values are set for D65 Rec709. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| node.active | int | 1 | If non-zero node is active. (default 0) | +| illuminantAdaptation.useBradfordTransform | int | 1 | If non-zero illuminant adaptation is enabled using Bradford transform. (default 1) | +| illuminantAdaptaton.inIlluminantWhite | float | 1 | Input illuminant white point. (default [0.3127 0.3290]) | +| illuminantAdaptation.outIlluminantWhite | float | 1 | Output illuminant white point. (default [0.3127 0.3290]) | +| inChromaticities.red | float[2] | 1 | Input chromaticities red point. (default [0.6400 0.3300]) | +| inChromaticities.green | float[2] | 1 | Input chromaticities green point. (default [0.3000 0.6000]) | +| inChromaticities.blue | float[2] | 1 | Input chromaticities blue point. (default [0.1500 0.0600]) | +| inChromaticities.white | float[2] | 1 | Input chromaticities white point. (default [0.3127 0.3290]) | +| outChromaticities.red | float[2] | 1 | Output chromaticities red point. (default [0.6400 0.3300]) | +| outChromaticities.green | float[2] | 1 | Output chromaticities green point. (default [0.3000 0.6000]) | +| outChromaticities.blue | float[2] | 1 | Output chromaticities blue point. (default [0.1500 0.0600]) | +| outChromaticities.white | float[2] | 1 | Output chromaticities white point. (default [0.3127 0.3290]) | + +### PipelineGroup, RVDisplayPipelineGroup, RVColorPipelineGroup, RVLinearizePipelineGroup, RVLookPipelineGroup and RVViewPipelineGroup + + +The PipelineGroup node and the RV specific pipeline nodes are group nodes that manages a pipeline of single input nodes. There is a single property on the node which determines the structure of the pipeline. The only difference between the various pipeline node types is the default value of the property. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| pipeline.nodes | string | 1 or more | The type names of the nodes in the managed pipeline from input to output order. | + +| Node Type | Default Pipeline | +| --- | --- | +| PipelineGroup | No Default Pipeline | +| RVLinearizePipelineGroup | RVLinearize | +| RVColorPipelineGroup | RVColor | +| RVLookPipelineGroup | RVLookLUT | +| RVViewPipelineGroup | No Default Pipeline | +| RVDisplayPipelineGroup | RVDisplayColor | + +### RVRetime + + +Retime nodes are in many of the group nodes to handle any necessary time changes to match playback between sources and views with different native frame rates. You can also use them for “artistic retiming” of two varieties.The properties in the “warp” component (see below) implement a key-framed “speed warping” variety of retiming, where the keys describe the speed (as a multiplicative factor of the target frame rate - so 1.0 implies no difference, 0.5 implies half-speed, and 2.0 implies double-speed) at a given input frame. Or you can provide an explicit map of output frames from input frames with the properties in the “explicit” component (see below). Note that the warping will still make use of what it can of the “standard” retiming properties (in particular the output fps and the visual scale), but if you use explicit retiming, none of the standard properties will have any effect. The “precedence” of the retiming types depends on the active flags: if “explicit.active” is non-zero, the other properties will have no effect., and if there is no explicit retiming, warping will be active if “warp.active” is true. Please note that neither speed warping nor explicit mapping does any retiming of the input audio. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| visual.scale | float | 1 | If extending the length scale is greater than 1.0. If decreasing the length scale is less than 1.0. | +| visual.offset | float | 1 | Number of frames to shift output. | +| audio.scale | float | 1 | If extending the length scale is greater than 1.0. If decreasing the length scale is less than 1.0. | +| audio.offset | float | 1 | Number of seconds to shift output. | +| output.fps | float | 1 | Output frame rate in frames per second. | +| warp.active | int | 1 | 1 if warping should be active. | +| warp.keyFrames | int | N | Input frame numbers at which target speed should change. | +| warp.keyRates | float | N | Target speed multipliers for each input frame number above (1.0 means no speed change). | +| explicit.active | int | 1 | 1 if an explicit mapping is provided and should be used. | +| explicit.firstOutputFrame | int | 1 | The output frame range provided by the Retime node will start with this frame. The last frame provided will be determined by the length of the array in the “inputFrames” property. | +| explicit.inputFrames | int | N | Each element in this array corresponds to an output frame, and the value of each element is the input frame number that will be used to provide the corresponding output frame. | + +### RVRetimeGroup + + +The RetimeGroup is mostly just a holder for a Retime node. It has a single property. + +| Name | Type | Size | Description | +| --- | --- | --- | --- | +| ui.name | string | 1 | This is a user specified name which appears in the user interface. | + +### RVSequence + + +Information about how to create a working EDL can be found in the User's Manual. All of the properties in the edl component should be the same size. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| edl.frame | int | N | The global frame number which starts each cut | +| edl.source | int | N | The source input number of each cut | +| edl.in | int | N | The source relative in frame for each cut | +| edl.out | int | N | The source relative out frame for each cut | +| output.fps | float | 1 | Output FPS for the sequence. Input nodes may be retimed to this FPS. | +| output.size | int[2] | 1 | The virtual output size of the sequence. This may not match the input sizes. | +| output.interactiveSize | int | 1 | If 1 then adjust the virtual output size automatically to the window size for framing. | +| output.autoSize | int | 1 | Figure out a good size automatically from the input sizes if 1. Otherwise use output.size. | +| mode.useCutInfo | int | 1 | Use cut information on the inputs to determine EDL timing. | +| mode.autoEDL | int | 1 | If non-0, automatically concatenate new sources to the existing EDL, otherwise do not modify the EDL | + +### RVSequenceGroup + + +The sequence group contains a chain of nodes for each of its inputs. The input chains are connected to a single RVSequence node which controls timing and switching between the inputs. + +| Name | Type | Size | Description | +| --- | --- | --- | --- | +| ui.name | string | 1 | This is a user specified name which appears in the user interface. | +| timing.retimeInputs | int | 1 | Retime all inputs to the output fps if 1 otherwise play back their frames one at a time at the output fps. | + +### RVSession + + +The session node is a great place to store centrally located information to easily access from any other node or location. Almost like a global grab bag. + +| Name | Type | Size | Description | +| --- | --- | --- | --- | +| matte.aspect | float | 1 | Centralized setting for the aspect ratio of the matte used in all sources. Float ratio of width divided by height. | +| matte.centerPoint | float[2] | 1 | Centralized setting for the center of the matte used in all sources. Value stored as X, Y in normalized coordinates. | +| matte.heightVisible | float | 1 | Centralized setting for the fraction of the source height that is still visible from the matte used in all sources. | +| matte.opacity | float | 1 | Centralized setting for the opacity of the matte used in all sources. 0 == clear 1 == opaque. | +| matte.show | int | 1 | Centralized setting to turn on or off the matte used in all sources. 0 == OFF 1 == ON. | + +### RVSoundTrack + + +Used to construct the audio waveform textures. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| audio.volume | float | 1 | Global audio volume | +| audio.balance | float | 1 | [-1,1] left/right channel balance | +| audio.offset | float | 1 | Globl audio offset in seconds | +| audio.mute | int | 1 | If non-0 audio is muted | + +### RVSourceGroup + + +The source group contains a single chain of nodes the leaf of which is an RVFileSource or RVImageSource. It has a single property. + +| Name | Type | Size | Description | +| --- | --- | --- | --- | +| ui.name | string | 1 | This is a user specified name which appears in the user interface. | + +### RVSourceStereo + + +The source stereo nodes are used to control independent eye transformations. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| stereo.swap | int | 1 | If non-0 swap the left and right eyes | +| stereo.relativeOffset | float | 1 | Offset distance between eyes, default = 0. Both eyes are offset. | +| stereo.rightOffset | float | 1 | Offset distance between eyes, default = 0. Only right eye is offset. | +| rightTransform.flip | int | 1 | If non-0 flip the right eye | +| rightTransform.flop | int | 1 | If non-0 flop the right eye | +| rightTransform.rotate | float | 1 | Right eye rotation in degrees | +| rightTransform.translate | float[2] | 1 | independent 2D translation applied only to right eye (on top of offsets) | + +### RVStack + + +The stack node is part of a stack group and handles control for settings like compositing each layer as well as output playback timing. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| output.fps | float | 1 | Output FPS for the stack. Input nodes may be retimed to this FPS. | +| output.size | int[2] | 1 | The virtual output size of the stack. This may not match the input sizes. | +| output.autoSize | int | 1 | Figure out a good size automatically from the input sizes if 1. Otherwise use output.size. | +| output.chosenAudioInput | string | 1 | Name of input which becomes the audio output of the stack. If the value is .all. then all inputs are mixed. If the value is .first. then the first input is used. | +| composite.type | string | 1 | The compositing operation to perform on the inputs. Valid values are: over, add, difference, -difference, and replace | +| mode.useCutInfo | int | 1 | Use cut information on the inputs to determine EDL timing. | +| mode.strictFrameRanges | int | 1 | If 1 match the timeline frames to the source frames instead of retiming to frame 1. | +| mode.alignStartFrames | int | 1 | If 1 offset all inputs so they start at same frame as the first input. | + +### RVStackGroup + + +The stack group contains a chain of nodes for each of its inputs. The input chains are connected to a single RVStack node which controls compositing of the inputs as well as basic timing offsets. + +| Name | Type | Size | Description | +| --- | --- | --- | --- | +| ui.name | string | 1 | This is a user specified name which appears in the user interface. | +| timing.retimeInputs | int | 1 | Retime all inputs to the output fps if 1 otherwise play back their frames one at a time at the output fps. | + +### RVSwitch + + +The switch node is part of a switch group and handles control for output playback timing. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| output.fps | float | 1 | Output FPS for the switch. This is normally determined by the active input. | +| output.size | int[2] | 1 | The virtual output size of the stack. This is normally determined by the active input. | +| output.autoSize | int | 1 | Figure out a good size automatically from the input sizes if 1. Otherwise use output.size. | +| output.input | string | 1 | Name of the active input node. | +| mode.useCutInfo | int | 1 | Use cut information on the inputs to determine EDL timing. | +| mode.alignStartFrames | int | 1 | If 1 offset all inputs so they start at same frame as the first input. | + +### RVSwitchGroup + + +The switch group changes it behavior depending on which of its inputs is “active”. It contains a single Switch node to which all of its inputs are connected. + +| Name | Type | Size | Description | +| --- | --- | --- | --- | +| ui.name | string | 1 | This is a user specified name which appears in the user interface. | + +### RVTransform2D + + +The 2D transform node controls the image transformations. This node is usually evaluated on the GPU. + +| Property | Type | Size | Description | +| --- | --- | --- | --- | +| transform.flip | int | 1 | non-0 means flip the image (vertically) | +| transform.flop | int | 1 | non-0 means flop the image (horizontally) | +| transform.rotate | float | 1 | Rotate the image in degrees about its center. | +| pixel.aspectRatio | float | 1 | If non-0 set the pixel aspect ratio. Otherwise use the pixel aspect ratio reported by the incoming image. | +| transform.translate | float[2] | 1 | Translation in 2D in NDC space | +| transform.scale | float[2] | 1 | Scale in X and Y dimensions in NDC space | +| stencil.visibleBox | float | 4 | Four floats indicating the left, right, top, and bottom in NDC space of a stencil box. | + +### RVViewGroup + + +The RVViewGroup node has no external properties. \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-ten.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-ten.md new file mode 100644 index 000000000..5bfc3ee9c --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-ten.md @@ -0,0 +1,267 @@ +# Chapter 10 - A Simple Package + +This first example will show how to create a package that defines some key bindings and creates a custom personal menu. You will not need to edit a .rvrc.mu file to do this as in previous versions.We'll be creating a package intended to keep all our personal customizations. To start with we'll need to make a Mu module that implements a new mode. At first won't do anything at all: just load at start up. Put the following in to a file called mystuff.mu. + +``` +use rvtypes; +use extra_commands; +use commands; + +module: mystuff { + +class: MyStuffMode : MinorMode +{ + method: MyStuffMode (MyStuffMode;) + { + init("mystuff-mode", + nil, + nil, + nil); + } +} + +\: createMode (Mode;) +{ + return MyStuffMode(); +} + +} // end module + +``` +Now we need to create a PACKAGE file in the same directory before we can create the package zip file. It should look like this: + +``` +package: My Stuff +author: M. VFX Artiste +version: 1.0 +rv: 3.6 +requires: '' + +modes: + - file: mystuff + load: immediate + +description: | +

M. VFX Artiste's Personal RV Customizations

+``` +Assuming both files are in the same directory, we create the zip file using this command from the shell: + +``` + shell> zip mystuff-1.0.rvpkg PACKAGE mystuff.mu +``` +The file mystuff-1.0.rvpkg should have been created. Now start RV, open the preferences package pane and add the mystuff-1.0.rvpkg package. You should now be able to install it. Make sure the package is both installed and loaded in your home directory's RV support directory so it's private to you.At this point, we'll edit the installed Mu file directly so we can see results faster. When we have something we like, we'll copy it back to the original mystuff.mu and make the rvpkg file again with the new code. Be careful not to uninstall the mystuff package while we're working on it or our changes will be lost. Alternately, for the more paranoid (and wiser), we could edit the file elsewhere and simply copy it onto the installed file.To start with let's add two functions on the \`\`<'' and \`\`>'' keys to speed up and slow down the playback by increasing and decreasing the FPS. There are two main this we need to do: add two method to the class which implement speeding up and slowing down, and bind those functions to the keys.First let's add the new methods after the class constructor MyStuffMode() along with two global bindings to the \`\`<'' and \`\`>'' keys. The class definition should now look like this: + +``` + ... + +class: MyStuffMode : MinorMode +{ + method: MyStuffMode (MyStuffMode;) + { + init("mystuff-mode", + [("key-down-->", faster, "speed up fps"), + ("key-down--<", slower, "slow down fps")], + nil, + nil); + } + +method: faster (void; Event event) + { + setFPS(fps() \* 1.5); + displayFeedback("%g fps" % fps()); + } + +method: slower (void; Event event) + { + setFPS(fps() \* 1.0/1.5); + displayFeedback("%g fps" % fps()); + } +} +``` +The bindings are created by passing a list of tuples to the init function. Each tuple contains three elements: the event name to bind to, the function to call when it is activated, and a single line description of what it does. In Mu a tuple is formed by putting parenthesis around comma separated elements. A list is formed by enclosing its elements in square brackets. So a list of tuples will have the form: + +``` + [ (...), (...), ... ] +``` +Where the \`\`...'' means \`\`and so on''. The first tuple in our list of bindings is: + +``` + (key-down-->, faster, speed up fps) +``` +So the event in this case is key-down–> which means the point at which the > key is pressed. The symbol faster is referring to the method we declared above. So faster will be called whenever the key is pressed. Similarily we bind slower (from above as well) to key-down–<. + +``` + ("key-down--<", slower, "slow down fps") +``` +And to put them in a list requires enclose the two of them in square brackets: + +``` + [("key-down-->", faster, "speed up fps"), + ("key-down--<", slower, "slow down fps")] +``` +To add more bindings you create more methods to bind and add additional tuples to the list.The python version of above looks like this: + +``` +from rv.rvtypes import * +from rv.commands import * +from rv.extra_commands import * + +class PyMyStuffMode(MinorMode): + + def __init__(self): + MinorMode.__init__(self) + self.init("py-mystuff-mode", + [ ("key-down-->", self.faster, "speed up fps"), + ("key-down--<", self.slower, "slow down fps") ], + None, + None) + + def faster(self, event): + setFPS(fps() * 1.5) + displayFeedback("%g fps" % fps(), 2.0); + + def slower(self, event): + setFPS(fps() * 1.0/1.5) + displayFeedback("%g fps" % fps(), 2.0); + + +def createMode(): + return PyMyStuffMode() +``` + +### 10.1 How Menus Work + + +Adding a menu is fairly straightforward if you understand how to create a MenuItem. There are different types of MenuItems: items that you can select in the menu and cause something to happen, or items that are themselves menus (sub-menu). The first type is constructed using this constructor (shown here in prototype form) for Mu: + +``` + MenuItem(string label, + (void;Event) actionHook, + string key, + (int;) stateHook); +``` +or in Python this is specified as a tuple: + +``` + ("label", actionHook, "key", stateHook) +``` +The actionHook and stateHook arguments need some explanation. The other two (the label and key) are easier: the label is the text that appears in the menu item and the key is a hot key for the menu item.The actionHook is the purpose of the menu item–it is a function or method which will be called when the menu item is activated. This is just like the method we used with bind() — it takes an Event object. If actionHook is nil, than the menu item won't do anything when the user selects it.The stateHook provides a way to check whether the menu item should be enabled (or greyed out)–it is a function or method that returns an int. In fact, it is really returning one of the following symbolic constants: NeutralMenuState, UncheckMenuState, CheckedMenuState, MixedStateMenuState, or DisabledMenuState. If the value of stateHook is nil, the menu item is assumed to always be enabled, but not checked or in any other state.A sub-menu MenuItem can be create using this constructor in Mu: + +``` + MenuItem(string label, + MenuItem[] subMenu); +``` +or a tuple of two elements in Python: + +``` + ("label", subMenu) +``` +The subMenu is an array of MenuItems in Mu or a list of menu item tuples in Python. Usually we'll be defining a whole menu — which is an array of MenuItems. So we can use the array initialization syntax to do something like this: + +``` + let myMenu = MenuItem {"My Menu", Menu { + {"Menu Item", menuItemFunc, nil, menuItemState}, + {"Other Menu Item", menuItemFunc2, nil, menuItemState2} +}} +``` +Finally you can create a sub-menu by nesting more MenuItem constructors in the subMenu. + +``` + MenuItem myMenu = {"My Menu", Menu { + {"Menu Item", menuItemFunc, nil, menuItemState}, + {"Other Menu Item", menuItemFunc2, nil, menuItemState2}, + {"Sub-Menu", Menu { + {"First Sub-Menu Item", submenuItemFunc1, nil, submenu1State} + }} + }}; +``` +in Python this looks like: + +``` + ("My Menu", [ + ("Menu Item", menuItemFunc, None, menuItemState), + ("Other Menu Item", menuItemFunc2, None, menuItemState2)]) +``` +You'll see this on a bigger scale in the rvui module where most the menu bar is declared in one large constructor call. + +### 10.2 A Menu in MyStuffMode + + +Now back to our mode. Let's say we want to put our faster and slower functions on menu items in the menu bar. The fourth argument to the init() function in our constructor takes a menu representing the menu bar. You only define menus which you want to either modify or create. The contents of our main menu will be merged into the menu bar.By merge into we mean that the menus with the same name will share their contents. So for example if we add the File menu in our mode, RV will not create a second File menu on the menu bar; it will add the contents of our File menu to the existing one. On the other hand if we call our menu MyStuff RV will create a brand new menu for us (since presumably MyStuff doesn't already exist). This algorithm is applied recursively so sub-menus with the same name will also be merged, and so on.So let's add a new menu called MyStuff with two items in it to control the FPS. In this example, we're only showing the actual init() call from mystuff.mu: + +``` + init("mystuff-mode", + [ ("key-down-->", faster, "speed up fps"), + ("key-down--<", slower, "slow down fps") ], + nil, + Menu { + {"MyStuff", Menu { + {"Increase FPS", faster, nil}, + {"Decrease FPS", slower, nil} + } + } + }); +``` +Normally RV will place the new menu (called \`\`MyStuff'') just before the Windows menu.If we wanted to use menu accelerators instead of (or in addition to) the regular event bindings we add those in the menu item constructor. For example, if we wanted to also use the keys - and = for slower and faster we could do this: + +``` + init("mystuff-mode", + [ ("key-down-->", faster, "speed up fps"), + ("key-down--<", slower, "slow down fps") ], + nil, + Menu { + {"MyStuff", Menu { + {"Increase FPS", faster, "="}, + {"Decrease FPS", slower, "-"} + } + } + }); +``` +The advantage of using the event bindings instead of the accelerator keys is that they can be overridden and mapped and unmapped by other modes and \`\`chained'' together. Of course we could also use > and < for the menu accelerator keys as well (or instead of using the event bindings).The Python version of the script might look like this: + +``` +from rv.rvtypes import * +from rv.commands import * +from rv.extra_commands import * + +class PyMyStuffMode(MinorMode): + + def __init__(self): + MinorMode.__init__(self) + self.init("py-mystuff-mode", + [ ("key-down-->", self.faster, "speed up fps"), + ("key-down--<", self.slower, "slow down fps") ], + None, + [ ("MyStuff", + [ ("Increase FPS", self.faster, "=", None), + ("Decrease FPS", self.slower, "-", None)] )] ) + + def faster(self, event): + setFPS(fps() * 1.5) + displayFeedback("%g fps" % fps(), 2.0); + + def slower(self, event): + setFPS(fps() * 1.0/1.5) + displayFeedback("%g fps" % fps(), 2.0); + + +def createMode(): + return PyMyStuffMode() +``` + +### 10.3 Finishing up + + +Finally, we'll create the final rvpkg package by copying mystuff.mu back to our temporary directory with the PACKAGES file where we originally made the rvpkg file.Next start RV and uninstall and remove the mystuff package so it no longer appears in the package manager UI. Once you've done this recreate the rvpkg file from scratch with the new mystuff.mu file and the PACKAGES file: + +```bash + shell> zip mystuff-1.0.rvpkg PACKAGES mystuff.mu +``` + +or if you're using python: + +```bash + shell> zip mystuff-1.0.rvpkg PACKAGES mystuff.py +``` + +You can now add the latest mysuff-1.0.rvpkg file back to RV and use it. In the future add personal customizations directly to this package and you'll always have a single file you can install to customize RV. diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-thirteen.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-thirteen.md new file mode 100644 index 000000000..8189e29d0 --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-thirteen.md @@ -0,0 +1,207 @@ +# Chapter 13 - Network Communication + +RV can communicate with multiple external programs via its network protocol. The mechanism is designed to function like a “chat” client. Once a connection is established, messages can be sent and received including arbitrary binary data.There are a number of applications which this enables: + +* **Controlling RV remotely.** E.g., a program which takes input from a dial and button board or a mobile device and converts the input into commands to start/stop playback or scrubbing in RV. +* **Synchronizing RV sessions across a network** . This is how RV's sync mode is implemented: each RV serves as a controller for the other. +* **Monitoring a Running RV** . For VFX theater dailies the RV session driving the dailies could be monitored by an external program. This program could then indicate to others in the facility when their shots are coming up. +* **A Display Driver for a Renderer** . Renders like Pixar's RenderMan have a plug-in called a display driver which is normally used to write out rendered frames as files. Frequently this type of plug-in is also used to send pixels to an external frame buffer (like RV) to monitor the renderer's progress in real time. It's possible to write a display driver that talks to RV using the network protocol and send it pixels as they are rendered. A more advanced version might receive feedback from RV (e.g. a selected rectangle on the image) in order to recommend areas the renderer should render sooner. + +Any number of network connections can be established simultaneously, so for example it's possible to have a synchronized RV session with a remote RV and drive it with an external hardware device at the same time. + +### 13.1 Example Code + + +There are two working examples that come with RV: the rvshell program and pyNetwork.py python example. The rvshell program uses a C++ library included with the distribution called TwkQtChat which you can use to make interfacing easier — especially if your program will use Qt. We highly recommend using this library since this is code which RV uses internally so it will always be up-to-date. The library is only dependent on the QtCore and QtNetwork modules.The pyNetwork example implements the network protocol using only python native code. You can use it directly in python programs. + +#### 13.1.1 Using rvshell + +To use rvshell, start RV from the command line with the network started and a default port of 45000 (to make sure it doesn't interfere with existing RV sessions): + +```bash + shell> rv -network -networkPort 45000 +``` + +Next start the rvshell program program from a different shell: + +```bash + shell> rvshell user localhost 45000 +``` + +Assuming all went well, this will start rvshell connected to the running RV. There are three things you can experiment with using rvhell: a very simple controller interface, a script editor to send portions of script or messages to RV manually, and a display driver simulator that sends stereo frames to RV.Start by loading a sequence of images or a quicktime movie into RV. In rvshell switch to the “Playback Control” tab. You should be able to play, stop, change frames and toggle full screen mode using the buttons on the interface. This example sends simple Mu commands to RV to control it. The feedback section of the interface shows the RETURN message send back from RV. This shows whatever result was obtained from the command.The “Raw Event” section of the interface lets you assemble event messages to send to RV manually. The default event message type is remote-eval which will cause the message data to be treated like a Mu script to execute. There is also a remote-pyeval event which does the same with Python (in which case you should type in Python code instead of Mu code). Messages sent this way to RV are translated into UI events. In order for the interface code to respond to the event something must have bound a function to the event type. By default RV can handle remote-eval and remote-pyeval events, but you can add new ones yourself.When RV receieves a remote-eval event it executes the code and looks for a return value. If a return value exists, it converts it to a string and sends it back. So using remote-eval it's possible to querry RV's current state. For example if you load an image into RV and then send it the command renderedImages() it will return a Mu struct as a string with information about the rendered image. Similarily, sending a remote-pyeval with the same command will return a Python dictionary as a string with the same information.The last tab “Pixels” can be used to emulate a display driver. Load a JPEG image into rvshell's viewer (don't try something over 2k — rvshell is using Qt's image reader). Set the number of tiles you want to send in X and Y, for example 10 in each. In RV clear the session. In rvshell hit the Send Image button. rvshell will create a new stereo image source in RV and send the image one tile at a time to it. The left eye will be the original image and the right eye will be its inverse. Try View → Stereo → Side by Side to see the results. + +#### 13.1.2 Using rvNetwork.py + +document here + +### 13.2 TwkQtChat Library + + +The TwkQtChat library is composed of three classes: Client, Connection, and Server. + +| | | +| --- | --- | +| sendMessage | Generic method to send a standard UTF-8 text message to a specific contact | +| sendData | Generic method to send a data message to a specific contact | +| broadcastMessage | Send a standard UTF-8 message to all contacts | +| sendEvent | Send an EVENT or RETURNEVENT message to a contact (calls sendMessage) | +| broadcastEvent | Send an EVENT or RETURNEVENT message to all contacts | +| connectTo | Initiate a connection to a specific contact | +| hasConnection | Query connection status to a contact | +| disconnectFrom | Force the shutdown of connection | +| waitForMessage | Block until a message is received from a specific contact | +| waitForSend | Block until a message is actually sent | +| signOff | Send a DISCONNECT message to a contact to shutdown gracefully | +| online | Returns true of the Server is running and listening on the port | + +Table 13.1:Important Client Member Functions + +| | | +| --- | --- | +| newMessage | A new message has been received on an existing connection | +| newData | A new data message has been received on an existing connection | +| newContact | A new contact (and associated connection) has been established | +| contactLeft | A previously established connection has been shutdown | +| requestConnection | A remote program is requesting a connection | +| connectionFailed | An attempted connection failed | +| contactError | An error occurred on an existing connection | + +Table 13.2:Client Signals + +A single Client instance is required to represent your process and to manage the Connections and Server instances. The Connection and Server classes are derived from the Qt QTcpSocket and QTcpServer classes which do the lower level work. Once the Client instance exists you can get pointer to the Server and existing Connections to directly manipulate them or connect their signals to slots in other QObject derived classes if needed.The application should start by creating a Client instance with its contact name (usually a user name), application name, and port on which to create the server. The Client class uses standard Qt signals and slots to communicate with other code. It's not necessary to inherit from it.The most important functions on the Client class are list in table [13.1](#important-client-member-functions) . + +### 13.3 The Protocol + + +There are two types of messages that RV can receive and send over its network socket: a standard message and a data message. Data messages can send arbitrary binary data while standard messages are used to send UTF-8 string data.The greeting is used only once on initial contact. The standard message is used in most cases. The data message is used primarily to send binary files or blocks of pixels to/from RV. + +#### 13.3.1 Standard Messages + +RV recognizes these types of standard messages: + +| | | +| --- | --- | +| MESSAGE | The string payload is subdivided into multiple parts the first of which indicates the sub-type of the message. The rest of the message is interpreted according to its sub-type. | +| GREETING | Sent by RV to a synced RV when negotiating the initial contact. | +| NEWGREETING | Sent by external controlling programs to RV during initial contact. | +| PINGPONGCONTROL | Used to negotiate whether or not RV and the connected process should exchange PING and PONG messages on a regular basis. | +| PING | Query the state of the other end of the connection — i.e. check and see if the other process is still alive and functioning. | +| PONG | Returned when a PING message is received to indicate state. | + +Table 13.3:Message TypesWhen an application first connects to RV over its TCP port, a greeting message is exchanged. This consists of an UTF-8 byte string composed of: + +| | | +| --- | --- | +| The string “NEWGREETING” | 1st word | +| The UTF-8 value 32 (space) | - | +| A UTF-8 integer composed of the characters [0-9] with the value **N** + **M** + 1 indicating the number of bytes remaining in the message | 2nd word | +| The UTF-8 value 32 (space) | - | +| Contact name UTF-8 string (non-whitespace) | **N** bytes | +| The UTF-8 value 32 (space) | 1 byte | +| Application name UTF-8 string (non-whitespace) | **M** bytes | + +Table 13.4:Greeting MessageIn response, the application should receive a NEWGREETING message back. At this point the application will be connected to RV.A standard message is a single UTF-8 string which has the form: + +| | | +| --- | --- | +| The string “MESSAGE” | 1st word | +| The UTF-8 value 32 (space) | - | +| A UTF-8 integer composed of the characters [0-9] the value of which is **N** indicating the size of the remaining message | 2nd word | +| The UTF-8 value 32 (space) | - | +| The message payload (remaining UTF-8 string) | **N** bytes | + +Table 13.5:Standard MessageWhen RV receives a standard message (MESSAGE type) it will assume the payload is a UTF-8 string and try to interpret it. The first word of the string is considered the sub-message type and is used to decide how to respond: + +| | | +| --- | --- | +| EVENT | Send the rest of the payload as a UI event (see below) | +| RETURNEVENT | Same as EVENT but will result in a response RETURN message | +| RETURN | The message is a response to a recently received RETURNEVENT message | +| DISCONNECT | The connection should be disconnected | + +Table 13.6:Sub-Message TypesThe EVENT and RETURNEVENT messages are the most common. When RV receives an EVENT or RETURNEVENT message it will translate it into a user interface event. The additional part of the string (after EVENT or RETURNEVENT) is composed of: + +| | | +| --- | --- | +| EVENT or RETURNEVENT | UTF-8 string identifying the message as an EVENT or RETURNEVENT message. | +| space character | - | +| non-whitespace-event-name | The event that will be sent to the UI as a string event (e.g. remote-eval). This can be obtained from the event by calling event.name()in Mu or Python | +| space character | - | +| non-whitespace-target-name | Present for backwards compatibility only. We recommend you use a single “\*” character to fill this slot. | +| space character | - | +| UTF-8 string | The string event contents. Retrievable with event.contents() in Mu or Python. | + +Table 13.7:EVENT MessagesFor example the full contents of an EVENT message might look like: + +``` + MESSAGE 34 EVENT my-event-name red green blue +``` + +The first word indicates a standard message. The next word (34) indicates the length of the rest of the data. EVENT is the message sub-type which further specifies that the next word (my-event-name) is the event to send to the UI with the rest of the string (red green blue) as the event contents.If a UI function that receives the event sets the return value and the message was a RETURNEVENT, then a RETURN will be sent back. A RETURN will have a single string that is the return value. An EVENT message will not result in a RETURN message. + +| | | +| --- | --- | +| RETURN | UTF-8 string identifying the message as an RETURN message. | +| space character | - | +| UTF-8 string | The string event returnContents(). This is the value set by setReturnContents() on the event object in Mu or Python. | + +Table 13.8:RETURN MessageGenerally, when a RETURNEVENT is sent to your application, a RETURN should be sent back because the other side may be blocked waiting. It's ok to send an empty RETURN. Normally, RV will not send EVENT or RETURNEVENT messages to other non-RV applications. However, it's possible that this could happen while connected to an RV that is also engaged in a sync session with another RV.Finally a DISCONNECT message comes with no additional data and signals that the connection should be closed. + +##### Ping and Pong Messages + +There are three lower level messages used to keep the status of the connection up to date. This scheme relies on each side of the connection returning a PONG message if it ever receives a PING message whenever ping pong messages are active.Whether or not it's active is controlled by sending the PINGPONGCONTROL message: when received, if the payload is the UTF-8 value “1” then PING messages should be expected and responded to. If the value is “0” then responding to a PING message is not mandatory.For some applications especially those that require a lot of computation (e.g. a display driver for a renderer) it can be a good to shut down the ping pong notification. When off, both sides of the connection should assume the other side is busy but not dead in the absence of network activity. + +| Message | Description | Full message value | +| --- | --- | --- | +| PINGPONGCONTROL | A payload value of “1” indicates that PING and PONG messages should be used | PINGPONGCONTROL 1 (1 or 0) | +| PING | The payload is always the character “p”. Should result in a PONG response | PING 1 p | +| PONG | The payload is always “p”. Should be sent in response to a PING message | PONG 1 p | + +Table 13.9:PING and PONG Messages + +#### 13.3.2 Data Messages + +The data messages come it two types: PIXELTILE and DATAEVENT. These take the form: + +| | | +| --- | --- | +| PIXELTILE(parameters) -or- DATAEVENT(parameters) | 1st word | +| space character | - | +| A UTF-8 integer composed of the characters [0-9] the value of which is **N** indicating the size of the remaining message | 2nd word | +| space character | - | +| Data of size **N** | **N** bytes | + +Table 13.10:PIXELTILE and DATAEVENTThe PIXELTILE message is used to send a block of pixels to or from RV. When received by RV the PIXELTILE message is translated into a pixel-block event (unless another event name is specified) which is sent to the user interface. This message takes a number of parameters which should have no whitespace characters and separated by commas (“,”): + +| | | +| --- | --- | +| w | Width of data in pixels. | +| h | Height of the data in pixels. (If the height of the block of pixels is 1 and the width is the width of the image, the block is equivalent to a scanline.) | +| x | The horizontal offset of the pixel block relative to the image origin | +| y | The vertical offset of the pixel block relative to the image origin | +| f | The frame number | +| event-name | Alternate event name (instead of pixel-block). RV will only recognize pixel-block event by default. You can bind to other events however. | +| media | The name of the media associated with data. | +| layer | The name of the layer associated with the meda. This is analogous to an EXR layer | +| view | The name of the view associated with the media. This is analogous to an EXR view | + +Table 13.11:PIXELTILE MessageFor example, the PIXELTILE header to the data message might appear as: + +``` + PIXELTILE(media=out.9.exr,layer=diffuse,view=left,w=16,h=16,x=160,y=240,f=9) +``` + +Which would be parsed and used to fill fields in the Event type. This data becomes available to Mu and Python functions binding to the event. By default the Event object is sent to the insertCreatePixelBlock() function which fins the image source associated with the meda and inserts the data into the correct layer and view of the image. Each of the keywords in the PIXELTILE header is optional.The DATAEVENT message is similar to the PIXELTILE but is intended to be implemented by the user. The message header takes at least three parameters which are ordered (no keywords like PIXELTILE). RV will use only the first three parameters: + +| | | +| --- | --- | +| event-name | RV will send a raw data event with this name | +| target | Required but not currently used | +| content type string | An arbitrary string indicating the type of the content. This is available to the UI from the Event.contentType() function. | + +Table 13.12:DATAEVENT MessageFor example, the DATAEVENT header might appear as: + +``` + DATAEVENT(my-data-event,unused,special-data) +``` +Which would be sent to the user interface as a my-data-event with the content type “special-data”. The content type is retrievable with Event.contentType(). The data payload is available via Event.dataContents() method. \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-three.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-three.md new file mode 100644 index 000000000..2a9bc3c17 --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-three.md @@ -0,0 +1,292 @@ +# Chapter 3 - Writing a Custom GLSL Node + +RV can use custom shaders to do GPU accelerated image processing. Shaders are authored in a superset of the GLSL language. These GLSL shaders become image processing Nodes in RV's session. Note that nodes can be either “signed” or “unsigned”. As of RV6, nodes can be loaded by any product in the RV line (RV, RVIO). The most basic workflow is as follows: + +* Create a file with a custom GLSL function (the Shader) using RV's extended GLSL language. +* Create a GTO node definition file which references the Shader file. +* Test and adjust the shader/node as necessary. +* Place the node definition and shader in the RV_SUPPORT_PATH under the Nodes directory for use by other users. + +### 3.1 Node Definition Files + + +Node definition files are GTO files which completely describe the operation of an image processing node in the image/audio processing graph.A node definition appears as a single GTO object of type IPNodeDefinition. This makes it possible for a node definition to appear in a session file directly or in an external definition file which can contain a library of definition objects.The meat of a node definition is source code for a kernel function written in an augmented version of GLSL which is described below.The following example defines a node called "Gamma" which takes a single input image and applies a gamma correction: + +``` + GTOa (4) + +Gamma : IPNodeDefinition (1) +{ + node + { + string evaluationType = "color" + string defaultName = "gamma" + string creator = "Tweak Software" + string documentation = "Gamma" + int userVisible = 1 + } + + render + { + int intermediate = 0 + } + + function + { + string name = "main" # OPTIONAL + string glsl = "vec4 main (const in inputImage in0, const in vec3 gamma) { return vec4(pow(in0().rgb, gamma), in0().a); }" + } + + parameters + { + float[3] gamma = [ [ 0.4545 0.4545 0.4545 ] ] + } +} +``` + +### 3.2 Fields in the IPNodeDefinition + + +node.evaluationType + +one of: + +| | | +| --- | --- | +| color | one input, per-pixel operations only | +| filter | one input, multiple input pixels sampled to create one output pixel | +| transition | two inputs, an animated transition | +| merge | one or more inputs, typically per-pixel operation | +| combine | one input to node, many inputs to function, pulls views, layers, eyes, multiple frames from input | + +node.defaultName + +the default name prefix for newly instantiated nodes + +node.creator + +documentation about definition author + +node.documentation + +Possibly html documentation string. In practice this may be quite large + +node.userVisible + +if non-0 a user can create this node directly otherwise only programmatically + +render.intermediate + +if non-0 the node results are forced to be cached + +function.name + +the name of the entry point in source code. By default this is main + +function.fetches + +approximate number of fetches performed by function. This is meaningful for filters. E.g. a 3x3 blur filter does 9 fetches. + +function.glsl + +Source code for the function in the augmented GLSL language. Alternately this can be a file URL pointing to the location of a separate text file containing the source code. See below for more details on file URL handling. + +parameters + +bindable parameters should be given default values in the parameters component. Special variables need not be given default values. (e.g. input images, the current frame, etc). + +#### 3.2.1 The “combine” Evaluation Type + +A “combine” node will evaluate its single input once for each parameter to the shader of type "inputImage".The names of the inputImage parameters in the shader may be chosen to be meaningful to the shader writer; they are not meaningful to the evaluation of the combine node. The order of the inputImage parameters in the shader parameter list will correspond to the multiple evaluations of the node's input (see below).Each time the input is evaluated, there are a number of variations that can be made in the context by way of properties specified in the node definition. To be clear, these properties are specified in the “parameters” section of the node definition, but they are “evaluation parameters” not shader parameters. These are: + +| | | +| --- | --- | +| eye | stereo eye, int, 0 for left, 1 for right | +| channel | color channel, string, eg "R" or "Z" | +| layer | named image layer, string, typically from EXR file | +| view | named image view, string, typically from EXR file | +| frame | absolute frame number, int | +| offset | frame number offset, int | + +A context-modifying property has 3 parts: the name (see above), an "inputImage index" (the int tacked onto the name), and the value. The affect of the parameter is that the context of the evaluation of the input specified by the index will be modified by the value. So for example "int eye0 = 1" means that the "eye" parameter of the context used in the first evaluation of the input will be set to "1".So for example, suppose a “StereoDifference” has this definition: + +``` + StereoDifference : IPNodeDefinition (1) +{ + node + { + string evaluationType = "combine" + } + function + { + string glsl = "file://${HERE}/StereoQC.glsl" + } + parameters + { + int eye0 = 0 + int eye1 = 1 + } +} +``` +And this shader parameter list: + +``` + vec4 main (const in inputImage left, const in inputImage right) +``` +Then: + +* It's a Combine node, so it's single input can be evaluated multiple times. +* The shader has two inputImage parameters so the input will be evaluated twice. +* The node definition contains "eye" parameters, so the eye value of the evaluation context will differ in different evaluations of the input. +* In the first evaluation of the input (index = 0), the context's eye value will be set to 0, and in the second it will be set to 1. +* The results of each evaluation of the input is made available to the shader in the corresponding inputImage parameter. + +As another example, here's a “FrameBlend” node: + +``` + FrameBlend : IPNodeDefinition (1) +{ + node + { + string evaluationType = "combine" + } + function + { + string glsl = "file://${HERE}/FrameBlend.glsl" + } + parameters + { + int offset0 = -2 + int offset1 = -1 + int offset2 = 0 + int offset3 = 1 + int offset4 = 2 + } +} +``` +And this shader parameter list: + +``` + vec4 main (const in inputImage in0, + const in inputImage in1, + const in inputImage in2, + const in inputImage in3, + const in inputImage in4) +``` +So the result is that the input to the FrameBlend node will be evaluated 5 times, and in each case the evaluation context will have a frame value that is equal to the incoming frame value, plus the corresponding offset. Note that the shader doesn't know anything about this, and from it's point of view it has 5 input images. + +### 3.3 Alternate File URL + + +Language source code can be either inlined for a self contained definition or can be a modified file URL which points to an external file. An example file URL might be: + +``` + file:///Users/foo/glsl/foo_shader_source.glsl +``` +If the node definition reader sees a file URL it will also perform variable substitution from the environment and any special predefined variables. For example if the $HOME environment variable exists the following would be equivalent on a Mac: + +``` + file://${HOME}/glsl/foo_shader_source.glsl +``` +There is currently one special variable defined called $HERE which has the value of the directory in which the definition file lives. So if for example the node definition file lives in the filesystem like so: + +``` +/Users/foo/nodes/my_nodes.gto +/Users/foo/nodes/glsl/node1_source_code.glsl +/Users/foo/nodes/glsl/node2_source_code.glsl +/Users/foo/nodes/glsl/node3_source_code.glsl +``` +and it references the GLSL files mentioned above then valid file URLs for the source files would like this: + +``` +file://${HERE}/glsl/node1_source_code.glsl +file://${HERE}/glsl/node2_source_code.glsl +file://${HERE}/glsl/node3_source_code.glsl +``` + +### 3.4 Augmented GLSL Syntax + + +GLSL source code can contain any set of functions and global static data but may not contain any uniform block definitions. Uniform block values are managed by the underlying renderer. + +#### 3.4.1 The main() Function + +The name of the function which serves as the entry point must be specified if it's not main().The main() function must always return a vec4 indicating the computed color at the current pixel.For each input to a node there should be a parameter of type inputImage. The parameters are applied in the order they appear. So the first node image input is assigned to the first inputImage parameter and so on.There are four special parameters which are supplied by the renderer: + +| | | +| --- | --- | +| float frame | The current frame number (local to the node) | +| float fps | The current frame rate (local to the node) | +| float baseFrame | The current global frame number | +| float stereoEye | The current stereo eye (0=left,1=right,2=default) | + +Table 3.1:Special Parameters to main() FunctionAny additional parameters are searched for in the 'parameters' component of the node. When a node is instantiated this will be populated by additional parameters to the main() function.For example, the Gamma node defined above has the following main() function: + +``` +vec4 main (const in inputImage in0, const in vec3 gamma) +{ + vec4 P = in0(); + return vec4(pow(P.rgb, gamma), P.a); +} +``` +In this case the node can only take a single input and will have a property called parameters.gamma of type float[3]. By changing the gamma property, the user can modify the behavior of the Gamma node. + +#### 3.4.2 The inputImage Type + +A new type inputImage has been added to GLSL. This type represents the input images to the node. So a node with one image argument must take a single inputImage argument. Likewise, a two input node should take two such arguments.There are a number of operations that can be done on a inputImage object. For the following examples the parameter name will be called i. + +| | | +| --- | --- | +| i() | Returns the current pixel value as a vec4. Functions which only call this operator on inputImage parameters can be of type "color" | +| i(vec2 *OFF* ) | If *P* is the current pixel location this returns the pixel at *OFF* + *P* | +| i.size() | Returns a vec2 (width,height) indicating the size of the input image | +| i.st | Returns the absolute current pixel coordinates ([0,width], [0,height]) with swizzling | + +Table 3.2:Type inputImage OperationsUse of the inputImage type as a function argument is limited to the main() function. Use of the inputImage type as a function should be minimized where possible e.g. the result should be stored into a local variable and the local variable used there after. For example: + +``` +vec4 P = i(); +return vec4(P.rgb * 0.5, P.a); +``` +**NOTE** : The *st* value return by an inputImage has a value ranging from 0 to the width in X and 0 to the height in Y. So for example, the pixel value of the first pixel in the image is located at (0.5, 0.5) not at (0, 0). Similarily, the last pixel in the image is located at (width-0.5, height-0.5) not (width-1, height-1) as might be expected. See ARB_texture_rectangle for information on why this is. In GLSL 1.5 and greater the *rectangle* coordinates are built into the language. + +#### 3.4.3 The outputImage Type + +The type outputImage has also been added. This type provides information about the output framebuffer.The main() function may have a single outputImage parameter. You cannot pass an outputImage to auxiliary functions nor can you have outputImage parameter to an auxiliary function. You can pass the results of operations on the outputImage object to other functions.outputImage has the following operations: + +| | | +| --- | --- | +| w.st | Returns the absolute fragment coordinate with swizzling | +| w.size() | Returns the size of the output framebuffer as a vec2 | + +Table 3.3:Type outputImage Operations + +#### 3.4.4 Use of Samplers + +Samplers can be used as inputs to node functions. The sampler name and type must match an existing parameter property on the node. So for example a 1D sampler would correspond to a 1D property the value of which is a scalar array. A 3D sampler would have a type like float[3,32,32,32] if it were an RGB 32^3 LUT. + +| | | +| --- | --- | +| sampler1D | *type* [ *D* , *X* ] | +| sampler2D | *type* [ *D* , *X* , *Y* ] | +| sampler2DRect | *type* [ *D* , *X* , *Y* ] | +| sampler3D | *type* [ *D* , *X* , *Y* , *Z* ] | + +Table 3.4:Sampler to Parameter Type Correspondences + +In the above table, *D* would normally 1, 3, or 4 for scalar, RGB, or RGBA. A value of 2 is possible but unusual.Use the new style texture() call instead of the non-overloaded pre GLSL 1.30 function calls like texture3D() or texture2DRect(). This should be the case even when the driver only supports 1.20. + +### 3.5 Testing the Node Definition + + +Once you have a NodeDefinition GTO file that contains or references your shader code as described above, you can test the node as follows: + +1. Add the node definition file to the Nodes directory on your RV_SUPPORT_PATH. For example, on Linux, you can put it in $HOME/.rv/Nodes. If the GLSL code is in a separate file, it should be in the location specified by the URL in the Node Definition file.You can use the ${HERE}/myshader.glsl notation (described above) to indicate that the GLSL is to be found in the same directory. +2. Start RV and from the Session Manager add a node with the “plus” button or the right-click menu (“New Viewable”) by choosing “Add Node by Type” and entering the type name of the new node (“Gamma” in the above example). +3. At this point you might want to save a Session File for easy testing. +4. You can now iterate by changing your shader code or the parameter values in the Session File and re-running RV to test. + +### 3.6 Publishing the Node Definition + + +When you have tested sufficiently in RV and would like to make the new Node Definition available to other users running RV, RVIO, etc, you need to:**Make the Node Definition available to users** . RV will pick up Node Definition files from any Nodes sub-directory along the RV_SUPPORT_PATH. So your definitions can be distributed by simply inserting them into those directories, or by including them in an RV Package (any GTO/GLSL files in an RV Package will be added to the appropriate “Nodes” sub-directory when the Package is installed). With some new node types, you may want to distribute Python or or Mu code to help the user manage the creation and parameter-editing of the new nodes, so wrapping all that up in an RV Package would be appropriate in those cases. \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-twelve.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-twelve.md new file mode 100644 index 000000000..61a4f68cb --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-twelve.md @@ -0,0 +1,321 @@ +# Chapter 12 - Automated Color and Viewing Management + +Color management in RV can be broken into three separate issues: + +* Determination of the input color space +* Deciding whether the input color space should be converted to the linear working space and with what transform +* Displaying the working color space on a particular device possibly in a manner which simulates another device (e.g, film look on an LCD monitor). + +Each of the above corresponds to a set of features in RV which can be automated: + +* Examining particular image attributes it's often possible to determine color space. Some images may use a naming convention or may be located in a particular place on a file system which indicates its color space. It's even possible that a separate file or program needs to be executed to get the actual color space. +* Input spaces can be transformed to working spaces using the built in transforms like sRGB, gamma, or Cineon log space to linear space. If RV does not have a built-in transform for the color space, a file LUT (one per input source) may be used to interpolate independent channel functions using a channel LUT or a general function of R G and B channels using a 3D LUT. Values from a CDL can also be used to bring the input into the working color space. +* Ideally RV will have a fixed set of display transform which map a linear space to a display. This makes it possible to load multiple sets of images with differing color spaces, transform them to a common linear working space, and display them using a global display transform. RV has built-in transform for sRGB and gamma and can also use a channel or 3D LUT if a custom function is needed. + +In addition to the color issues there are a few others which might need to be detected and/or corrected: + +* Unrecorded or incorrect pixel aspect ratios (e.g., DPX files with inaccurate headers) +* Special mattes which should be used with particular images +* Incorrect frame numbers +* Incorrect fps +* Specific production information which is not located in the image (e.g., shot information, tracking information) + +RV lets you customize all of the above for your facility and workflow by hooking into the user interface code. The most important method of doing so is using special events generated by RV internally and setting internal state at that time. + +### 12.1 The source-group-complete Event + + +The source-group-complete event is generated whenever media is added to a session; this includes when a Source is created, or when the set of media held be a Source is modified. By binding a function to this event, it's possible to configure any color space or other image dependant aspects of RV at the time the file is added. This can save a considerable amount of time and headache when a large number of people are using RV in differing circumstances.See the sections below for information about creating a package which binds source-group-complete to do color management. + +### 12.2 The default source-group-complete behavior + + +RV binds its own color management function located in the source_setup.py file called sourceSetup(). It's a good idea to override or augment this package for use in production environments. For example, you may want to have certain default color behavior for technical directors using movie files which differs from how a coordinator might view them (the coordinator may be looking at movies in sRGB space instead of with a film simulation for example).RV's default color management tries to use good defaults for incoming file formats. Here's the complete behavior shown as a set of heuristics applied in order: + +1. If the incoming image is a TIFF file and it has no color space attribute assume it's linear +2. If the image is JPEG or a quicktime movie file (.mov) and there is no color space attribute assume it's in sRGB space +3. If there is an embedded ICC profile and that profile is for sRGB space use RV's internal sRGB space transform instead (because RV does not yet handle embedded ICC profiles) +4. If the image is TIFF and it was created by ifftoany, assume the pixel aspect ratio is incorrect and fix it +5. If the image is JPEG, has no pixel aspect ratio attribute and no density attribute and looks like it comes from Maya, fix the pixel aspect ratio +6. Use the proper built-in conversion for the color space indicated in the color space attribute of the image +7. Use the sRGB display transform if any color space was successfully determined for the input image(s) + +From the user's point of view, the following situations will occur: + +* A DPX or Cineon is loaded which is determined to be in Log space — turn on the built in log to linear converter +* A JPEG or Quicktime movie file is determined to be in sRGB space or if no space is specified assumed to be in sRGB space — apply the built-in sRGB to linear converter +* An EXR is loaded — assume it's linear +* A TIFF file with no color space indication is assumed to be linear, if it does have a color space use that. +* A PNG file with no color space is assumed linear, otherwise use the color space attribute in the file +* Any file with a pixel aspect ratio attribute will be assumed to be correct (unless it's determined to have come from Maya) +* The monitor's \`\`gamma'' will be accounted for automatically (because RV assumes the monitor is an sRGB device) + +In addition, the default color management implements two varieties of user-level control, as examples of what you can do from the scripting level.First, environment variables with a standard format can be used to control the linearization process for a given file type. An environment variable of the form “RV_OVERRIDE_TRANSFER_” will set the linearization transform for the specified file type (and this will override the default rules described above). For example, if the environment variable “RV_OVERRIDE_TRANSFER_TIF” is set to “sRGB” then all files with extension “tif” or “TIF” will be linearized with the sRGB transform. If you want, you can also specify the bit depth. So you could set RV_OVERRIDE_TRANSFER_TIF_8 to sRGB and RV_OVERRIDE_TRANSFER_TIF_32 to Linear. The transform function name must be one of the following standard transforms. (The number following “Gamma” is arbitrary.) + +| | +| --- | +| Linear | +| sRGB | +| Cineon Log | +| Viper Log | +| Rec709 | +| Gamma *f* | + +Table 12.1:Standard Linearization Transforms. + +Second, any of the above-described environment variable names and standard transform names can appear on the command line following the “-flags” option. + +### 12.3 Breakdown of sourceSetup() in the source_setup Package + + +The source_setup system package defines the default sourceSetup() function. This is where RV's default color management comes from. The function starts by parsing the event contents (which contains the name of the file, the type of source node, and the source node name) as well as setting up the regular expressions used later in the function: + +> **Note:** RV Python to implement the source_setup package. The actual sourceSetup() function in source_setup.py may differ from what is described here. + +``` + args = event.contents().split(";;") + group = args[0] + fileSource = groupMemberOfType(group, "RVFileSource") + imageSource = groupMemberOfType(group, "RVImageSource") + source = fileSource if imageSource == None else imageSource + linPipeNode = groupMemberOfType(group, "RVLinearizePipelineGroup") + linNode = groupMemberOfType(linPipeNode, "RVLinearize") + lensNode = groupMemberOfType(linPipeNode, "RVLensWarp") + fmtNode = groupMemberOfType(group, "RVFormat") + tformNode = groupMemberOfType(group, "RVTransform2D") + lookPipeNode = groupMemberOfType(group, "RVLookPipelineGroup") + lookNode = groupMemberOfType(lookPipeNode, "RVLookLUT") + typeName = commands.nodeType(source) + fileNames = commands.getStringProperty("%s.media.movie" % source, 0, 1000) + fileName = fileNames[0] + ext = fileName.split('.')[-1].upper() + igPrim = self.checkIgnorePrimaries(ext) + mInfo = commands.sourceMediaInfo(source, None) +``` +The event.contents() function returns a string which might look something like this: + +``` + sourceGroup000000;;new +``` + +The split() function is used to create a dynamic array of strings to extract the source group's name. The nodes associated with the source group are then located and the media names are taken from the source node. The source node is either an RVImageSource which stores its image data directly in the session or an RVFileSource which references media on the filesystem. Both of these node types have a media component which contains the actual media names (usually a single file in the case of an RVFileSource node). + +There are three pipeline group nodes in each source group node and one pipeline group in the display group. For the default source_setup the linearize pipeline group is need to get the default RVLinearize node it contains.The next section of the function iterates over the image attributes and caches the ones we're interested in. The most important of these is the Colorspace attribute which is set by the file readers when the image color space is known. + +``` + srcAttrs = commands.sourceAttributes(source, fileName) + attrDict = dict(zip([i[0] for i in srcAttrs],[j[1] for j in srcAttrs])) + attrMap = { + "ColorSpace/ICC/Description" : "ICCProfileDesc", + "ColorSpace" : "ColorSpace", + "ColorSpace/Transfer" : "TransferFunction", + "ColorSpace/Primaries" : "ColorSpacePrimaries", + "DPX-0/Transfer" : "DPX0Transfer", + "ColorSpace/Conversion" : "ConversionMatrix", + "JPEG/PixelAspect" : "JPEGPixelAspect", + "PixelAspectRatio" : "PixelAspectRatio", + "JPEG/Density" : "JPEGDensity", + "TIFF/ImageDescription" : "TIFFImageDescription", + "DPX/Creator" : "DPXCreator", + "EXIF/Orientation" : "EXIFOrientation", + "EXIF/Gamma" : "EXIFGamma"} + for key in attrMap.keys(): + try: + exec('%s = "%s"' % (attrMap[key],attrDict[key])) + except KeyError: + pass +``` +The function sourceAttributes() returns the image attributes for a given file in a source. In this case we're passing in the source and file which caused the event. The return value of the function is a dynamic array of tuples of type (string,string) where the first element is the name of the attribute and the second is a string representation of the value. Each iteration through the loop, the next tuple is used to assign the attribute value to the a variable with name of the attribute.The variables ICCProfileName, Colorspace, JPEGPixelAspect, etc, are all variable of type string which are defined earlier in the function.Before getting to the meat of the function, there are two helper functions declared: setPixelAspect() and setFileColorSpace().The next major section of the function matches the file name against the regular expressions that were declared at the beginning and against the values of some of the attributes that were cached. + +``` + # + # Rules based on the extension + # + + if (ext == 'DPX'): + if (DPXCreator == "AppleComputers.libcineon" or + DPXCreator == "AUTODESK"): + # + # Final Cut's "Color" and Maya write out bogus DPX + # header info with the aspect ratio fields set + # improperly (to 0s usually). Properly undefined DPX + # headers do not have the value 0. + # + + if (int(PixelAspectRatio) == 0): + self.setPixelAspect(lensNode, 1.0) + elif (DPXCreator == "Nuke" and + (ColorSpace == "" or ColorSpace == "Other (0)") and + (DPX0Transfer == "" or DPX0Transfer == "Other (0)")): + # + # Nuke produces identical (uninformative) dpx headers for + # both Linear and Cineon files. But we expect Cineon to be + # much more common, so go with that. + # + + TransferFunction = "Cineon Log" + elif (ext == 'TIF' and TransferFunction == ""): + # + # Assume 8bit tif files are sRGB if there's no other indication; + # fall back to linear. + # + + if (mInfo['bitsPerChannel'] == 8): + TransferFunction = "sRGB" + else: + TransferFunction = "Linear" + + elif (ext in ['JPEG','JPG','MOV','AVI','MP4'] and TransferFunction == ""): + # + # Assume jpeg/mov is in sRGB space if none is specified + # + + TransferFunction = "sRGB" + elif (ext in ['J2C','J2K','JPT','JP2'] and ColorSpacePrimaries == "UNSPECIFIED"): + # + # If we're assuming XYZ primaries, but ignoring primaries just set + # transfer to sRGB. + # + + if (igPrim): + TransferFunction = "sRGB"; + + if (igPrim): + commands.setIntProperty(linNode + ".color.ignoreChromaticities", [1], True) + + if (ICCProfileDesc != ""): + # + # Hack -- if you see sRGB in a color profile name just use the + # built-in sRGB conversion. + # + + if ("sRGB" in ICCProfileDesc): + TransferFunction = "sRGB" + else: + TransferFunction = "" + + if (TIFFImageDescription == "Image converted using ifftoany"): + # + # Get around maya bugs + # + + print("WARNING: Assuming %s was created by Maya with a bad pixel aspect ratio\n" % fileName) + self.setPixelAspect(lensNode, 1.0) + + if (JPEGPixelAspect != "" and JPEGDensity != ""): + info = commands.sourceMediaInfo(source, fileName) + attrPA = float(JPEGPixelAspect) + imagePA = float(info['width']) / float(info['height']) + testDiff = attrPA - 1.0 / imagePA + + if ((testDiff < 0.0001) and (testDiff > -0.0001)): + # + # Maya JPEG -- fix pixel aspect + # + + print("WARNING: Assuming %s was created by Maya with a bad pixel aspect ratio\n" % fileName) + self.setPixelAspect(lensNode, 1.0) + + if (EXIFOrientation != ""): + # + # Some of these tags are beyond the internal image + # orientation choices so we need to possibly rotate, etc + # + + if not self.definedInSessionFile(tformNode): + rprop = tformNode + ".transform.rotate" + if (EXIFOrientation == "right - top"): + commands.setFloatProperty(rprop, [90.0], True) + elif (EXIFOrientation == "right - bottom"): + commands.setFloatProperty(rprop, [-90.0], True) + elif (EXIFOrientation == "left - top"): + commands.setFloatProperty(rprop, [90.0], True) + elif (EXIFOrientation == "left - bottom"): + commands.setFloatProperty(rprop, [-90.0], True) +``` +At this point in the function the color space of the input image will be known or assumed to be linear. Finally, we try to set the color space (which will result in the image pixels being converted to the linear working space). If this succeeds, use sRGB display as the default. + +``` + if (not noColorChanges): + # + # Assume (in the absence of info to the contrary) any 8bit file will be in sRGB space. + # + if (TransferFunction == "" and mInfo['bitsPerChannel'] == 8): + TransferFunction = "sRGB" + + # + # Allow user to override with environment variables + # + TransferFunction = self.checkEnvVar(ext, mInfo['bitsPerChannel'], TransferFunction) + + if (self.setFileColorSpace(linNode, TransferFunction, ColorSpace)): + + # + # The default display correction is sRGB if the + # pixels can be converted to (or are already in) + # linear space + # + # For gamma instead do this: + # + # setFloatProperty("#RVDisplayColor.color.gamma", float[] {2.2}, true); + # + # For a linear -> screen LUT do this: + # + # readLUT(lutfile, "#RVDisplayColor", true); + # + # If this is not the first source, assume that user or source_seetup + # has already set the desired display transform + + if len(commands.sources()) == 1: + self.setDisplayFromProfile() +``` + +### 12.4 Setting up 3D and Channel LUTs + + +The default source-group-complete event function does not set up any non-built-in transforms. When you need to automatically apply a LUT, as a file, look, or a display LUT, you need to do the following: + +``` + readLUT(file, nodeName, True) +``` +The nodeName will be \`\`#RVDisplayColor'' (to refer to it by type) for the display LUT. For a file or look LUT, you use the associated node name for the color node — in the default sourceSetup() function this would be the linNode variable. The file parameter to readLUT() will be the name of the LUT file on disk and can be any of the LUT types that RV reads. + +### 12.5 Setting CDL Values From File + + +As with using LUT files to fill in where built-in transforms do not cover your needs, you can read in CDL property values from a file. Use the following to read values from a CDL file on disk: + +``` + readCDL(file, nodeName, True) +``` +When using readCDL the “nodeName” should be that of the targeted RVColor or RVLookLUT node to which you are applying the CDL values read from “file”. In the default RV graph you will find CDL properties to set in the RVColor and RVLookLUT nodes for each source, but there are none out-of-the-box in the display pipeline. However, you can add RVColor or RVLookLUT nodes to any pipeline you need CDL control that does not have them by default.You can also add RVCDL nodes where you want CDL control, but these nodes do not require the use of readCDL. With RVCDL nodes you only need to set the node's node.file property and it will automatically load and parse the file from the path provided. Errors will be thrown if the file provided is invalid. + +### 12.6 Building a Package For Color Management + + +The recommend way to handle all event bindings is via a python package. To customize color management you can either create a new package from scratch as described here, or copy, rename, and hack the existing source_setup package.The use of source-group-complete is no different from any other event. By creating a package you can override the existing behavior or modify it. It also makes it possible to have layers of color management packages which (assuming they don't contradict each other) can collectively create a desired behavior. + +``` +from rv import rvtypes, commands, extra_commands +import os, re + +class CustomColorManagementMode(rvtypes.MinorMode): + + def sourceSetup (self, event, noColorChanges=False): + + // do work on the new source here + event.reject() + + def __init__(self): + rvtypes.MinorMode.__init__(self) + self.init("Source Setup", + None, + None, + [ ("source-group-complete", self.sourceSetup, "Color and Geometry Management") ], + "source_setup", + 20) + +def createMode(): + return CustomColorManagementMode() +``` +Note that we use the sortKey “source_setup” and the sortOrder “20”. This will ensure that our additional sourceSetup runs after the default **color** management.The included optional package “ocio_source_setup” is a good example of a package that does additional source setup. \ No newline at end of file diff --git a/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-two.md b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-two.md new file mode 100644 index 000000000..591314b0d --- /dev/null +++ b/doc/rv-manuals/rv-reference-manual/rv-reference-manual-chapter-two.md @@ -0,0 +1,263 @@ +# Chapter 2 - Image Processing Graph + +The UI needs to communicate with the core part of RV. This is done in two ways: by calling special command functions (commands) which act directly on the core (e.g. play() causes it to start playing), or by setting variables in the underlying image processing graph which control how images will be rendered.Inside each session there is a *directed acyclic graph* (DAG) which determines how images and audio will be evaluated for display. The DAG is composed of *nodes* which are themselves collections of *properties* .A node is something that produces images and/or audio as output from images and audio inputs (or no inputs in some cases). An example from RV is the *color* node; the color node takes images as input and produces images that are copies of the input images with the hue, saturation, exposure, and contrast potentially changed.A *property* is a state variable. The node's properties as a whole determine how the node will change its inputs to produce its outputs. You can think of a node's properties as *parameters* that change its behavior.RV's session file format (.rv file) stores all of the nodes associated with a session including each node's properties. So the DAG contains the complete state of an RV session. When you load an .rv file into RV, you create a new DAG based on the contents of the file. Therefore, *to change anything in RV that affects how an image looks, you must change a property in some node in its DAG* .There are a few commands which RV provides to get and set properties: these are available in both Mu and Python.Finally, there is one last thing to know about properties: they are arrays of values. So a property may contain zero values (it's empty) or one value or an array of values. The get and set functions above all deal with arrays of numbers even when a property only has a single value.[16](rv-reference-manual-chapter-sixteen.md#chapter-16-node-reference) lists all properties and their function for each node type. + +### 2.1 Top-Level Node Graph + + +When RV is started with e.g. two media (movies, file sequences) it will create two top-level group nodes: one for each media source. These are called RVSourceGroup nodes. In addition, there are four other top-level group nodes created and one display group node for each output device present on the system. + +![2_se_top_level.png](../../images/rv-reference-manual-2-rv-cxx-se-rv4-top-level-01.png) + +Figure 2.1:Top-Level node graph when two sources are present. + +There is always a default layout (RVLayoutGroup), sequence (RVSequenceGroup), and stack node (RVStackGroup) as well as a view group node (RVViewGroup). The view group is connected to each of the active display groups (RVDisplayGroup). There is only one input to the view group and that input determines what the user is seeing in the image viewer. When the user changes views, the view group input is switch to the node the user wishes to see. For example, when the user looks at one of the sources (not in a sequence) the view group will be connected directly to that source group.New top level nodes can be created by the user. These nodes can have inputs of any other top level node in the session other than the view group and display groups.In the scripting languages, the nodes are referred to by internal name. The internal name is not normally visible to the user, but is used extensively in the session file. Most of the node graph commands use internal node names or the name of the node's type. + +| Command | Mu Return Type | Python Return Type | Description | +| --- | --- | --- | --- | +| nodes() | string[] | Unicode String List | Returns an array of all nodes in the graph. | +| nodesOfType (string *typename* ) | string[] | Unicode String List | Returns all nodes in the graph of the specified type | +| nodeTypes() | string[] | Unicode String List | Returns an array of all node types known to the application | +| nodeType (string *nodename* ) | string | Unicode String | Returns the type of the node specified by *nodename* | +| deleteNode (string *nodename* ) | void | None | Deletes the node specified by *nodename* | +| setViewNode (string *nodename* ) | void | None | Connect the specified node to the view group | +| newNode (string *typename* , string *nodename* = nil) | string | Unicode String | Create a node of type *typename* with name *nodename* or if *nodename* is nil use a default name | +| nodeConnections (string *nodename* , bool *traverseGroups* = false) | (string[],string[]) | Tuple of two Unicode String Lists | Return a tuple of nodes connected to the specified node: the first as an array of input node names, the second as an array of output node names. | +| nodesExists (string *nodename* ) | bool | Bool | Returns true if the specified node exists false otherwise | +| setNodeInputs (string *nodename* , string[] *inputNodes* ) | void | None | Connect a nodes inputs to an array of node names | +| testNodeInputs (string *nodename* , string[] *inputNodes* ) | string | Unicode String | Test the validity of a set of input nodes for *nodename* . If nil is returned than the inputs are valid. If a string is returned than the inputs are not valid and the string contains a user readable reason why they are not. | + +Table 2.1:Commands used to manage nodes in the graph + +### 2.2 Group Nodes and Pipeline Groups + + +A group node is composed of multiple member nodes. The graph connectivity is determined by the value of the group node's properties or it is fixed. Group nodes can contain other group nodes. The member nodes are visible to the user interface scripting languages and their node names are unique in the graph. Nodes may only be connected to nodes that are members of the same group. In the case of top level nodes they can be connected to other top level nodes. + +A pipeline group is a type of group node that connects it members into a single pipeline (no branches). Every pipeline group has a string array property called pipeline.nodes which determines the types of the nodes in the pipeline and the order in which they are connected. Any node type other than view and display group nodes can be specified in the pipeline.nodes property.Each type of pipeline group has a default pipeline. Except for the RVLinearizePipelineGroup which has two nodes in its default pipeline all others have a single node with the exception of the view pipeline which is empty by default. By modifying the pipeline.nodes property in any of these pipeline groups the default member nodes can either be swapped out, removed completely, or additional nodes can be inserted.For example. the following python code will set the view pipeline to use a user defined node called “FilmLook”: + +``` + setStringProperty("#RVViewPipelineGroup.pipeline.nodes", ["FilmLook"], True) +``` + +### 2.3 Source Group Node + + +The source group node (RVSourceGroup) has fixed set of nodes and three pipeline groups which can be modified to customize the source color management. + +![3_source_group.png](../../images/rv-reference-manual-3-rv-cxx-rv4-source-group-02.png) + +Figure 2.2: Source Group Internals + +The source group takes no inputs. There is eiher an RVFileSource or an RVImageSource node at the leaf position of the source group. A file source contains the name of the media that is provided by the source. An image source contains raw pixels of its media (usually obtained directly from a renderer etc).The source group is responsible for linearizing the incoming pixel data, possibly color correcting it and applying a look, and holding per-source annotation and transforms. Any of these operations can be modified by changing property values on the member nodes of the source group.Pixels are expected to be in the working space (normally linear) after existing the source group. + +| Command | Mu Return Type | Python Return Type | Description | +| --- | --- | --- | --- | +| sources () | (string,int,int,intfloat,bool,bool)[] | Same | Returns an array of media info for all loaded media. | +| sourcesAtFrame (int *frame* ) | string[] | String Array | Returns array of source node names (RVFileSource and/or RVImageSource). This is equivalent to nodesOfType(“RVSource”) | +| sourceAttributes (string *nodename* , string *medianame* = nil) | (string,string)[] | Array of (String,String) | Returns an array of image attribute name/value pairs at the current frame. The sourceName can be the node name or the source path as returned by PixelImageInfo, etc. The optional media argument can be used to constraint the attributes to that media only. | +| sourceMediaInfo (string *nodename* , string *medianame* = nil) | SourceMediaInfo | Dictionary | Returns a SourceMediaInfo structure for the given source and optional media. The SourceMediaInfo supplies geometric and timing information about the image and sequence | +| sourceDisplayChannelNames (string *nodename* ) | string[] | String Array | Returns the names of channels in a source which are mapped to the display RGBA channels | +| addSource (string *filename* , string tag = nil) | void | None | Creates a new source group with the specified media | +| addSource (string[] *filename* s, string tag = nil) | void | None | Creates a new source group with the specified media | +| addSourceBegin() | void | None | Optional call providing a fast add source mechanism when adding multiple sources which postpones connecting the added sources to the default views' inputs until after the corresponding addSourceEnd() is called. The way to enable this optimization is to call addSourceBegin() first, followed by a bunch of addSource() calls, and then end with addSourceEnd(). | +| addSourceEnd() | void | None | Optional call providing a fast add source mechanism when adding multiple sources which postpones connecting the added sources to the default views' inputs until after the corresponding addSourceEnd() is called. The way to enable this optimization is to call addSourceBegin() first, followed by a bunch of addSource() calls, and then end with addSourceEnd(). | +| addSources(string[] sources, string tag = “”, bool processOpts = false, bool merge = false) | void | None | Add a new source group to the session (see addSource). This function adds the requested sources asynchronously. In addition to the "incoming-source-path" and "new-source" events generated for each source. A "before-progressive-loading" and "after-progressive-loading" event pair will be generated at the appropriate times. An optional tag can be provided which is passed to the generated internal events. The tag can indicate the context in which the addSource() call is occurring (e.g. drag, drop, etc). The optional processOpts argument can be set to true if there are 'option' states like -play, that should be processed after the loading is complete.Note that sources can be single movies/sequences or you can use the "[]" notation from the command line to specify multiple files for the source, like stereo layers, or additional audio files. Per-source command-line flags can also be used here, but the flags should be marked by a "+" rather than a "-". Also note that each argument is a separate element of the input string array.
For example a single stereo source might look like string[] {"[", "left.mov", "right.mov", "+rs", "1001", "]" } | +| addSourceVerbose (string *filename* [], string tag = nil) | string | String | Creates a new source group with the specified media. Returns the name of the source node created. | +| addToSource (string *filename* , string tag = nil) | void | None | Adds media to an existing source group | +| addToSource (string[] *filename* s, string tag = nil) | void | None | Adds media to an existing source group | +| setSourceMedia (string *nodename* , string[] *filename* s, string tag = nil) | void | None | Replace all media in the given RVFileSource node with new media with optional tag | +| relocateSource (string *nodename* , string *oldfilename* s, string *newfilename* ) | void | None | Replace media (one) in the specified RVFilesSource source with new media | +| relocateSource (string *oldfilename* s, string *newfilename* ) | void | None | Replace media (one) in the current RVFilesSource source with new media | +| newImageSource (string *mediaName* , int *width* , int *height* , int *uncropWidth* , int *uncropHeight* , int *uncropX* , int *uncropY* , float *pixelAspect* , int *channels* , int *bitsPerChannel* , bool *floatingPoint* , int *startFrame* , int *endFrame* , float *fps* , string[] *layers* = nil, string[] *views* = nil ) | string | String | Create a new source group with an image source as the media. The name of the newly created image source node is returned. | +| sourceMedia (string *nodename* ) | (string,string[],string[]) | Same | Returns tuples describing media in nodename. This command only returns information about the primary media and is deprecated. Use sourceMediaInfo() instead. | + +Table 2.2:Commands used to manage and create source groups + +#### 2.3.1 Progressive Source Loading + +If you need to, RV can load sources asynchronously, also known as progressive source loading. Progessive source loading is turned off by default. + +**Use one of these methods to enable asynchronous source loading:** +- Set `setProgressiveSourceLoading = true` +- With the command line argument: `-progressiveSourceLoading 1` +- With the environment variable: `RV_PROGRESSIVE_SOURCE_LOADING` + +> Note: `setProgressiveSourceLoading` affects the behaviour of the following scripting commands: +> - addSource() +> - addSources() +> - addSourceVerbose() +> - addSourcesVerbose() + +When progressive source loading is disabled (default setting), the sources are loaded synchronously. This creates the sequence of events: + +1. before-progressive-loading +1. source-group-complete media 1 +1. source-group-complete media 2 +1. after-progressive-loading + +When progressive source loading is enabled, RV loads the sources asynchronously: + +1. RV creates a movie placeholder in the graph called a movieProxie which has a default duration of 20 frames. +1. It dispatches the actual loading of the media as a work item to a pool of worker threads. +1. Once the media completes the loading operation, the movieProxie placeholder is replaced with the actual movie. + +You can expect this sequence of events: + +1. before-progressive-loading +1. before-progressive-proxy-loading +1. source-group-proxy-complete media_1 +1. source-group-proxy-complete media_2 +1. after-progressive-proxy-loading +1. source-group-complete media 1 +1. source-group-complete media 2 +1. after-progressive-loading + +Enabling progressive source loading significantly increases the complexity of scripting. Consider the following example: + +```python +rv.commands.addSource('/my/clip/1') +rv.commands.setFPS(60.00) +``` + +- With progressive source loading disabled (default setting), the script behaves as expected: the source is loaded with a frame rate set at 60 fps. + +- With progressive source loading enabled, the script doesn't behave as expected. While the source is loaded asynchronously, the frame rate is set temporarily to 60 fps. It's only once the source is completely loaded that the frame rate is set to the source's native rate. + +`progressiveSourceLoading` returns the loading state of the current progressive source. + +### 2.4 View Group Node + + +The view group (RVViewGroup) is responsible for viewing transforms and is the final destination for audio in most cases. The view group is also responsible for rendering any audio waveform visualization.Changing the view in RV is equivalent to changing the input of the view group. There is only one view group in an RV session.The view group contains a pipeline into which arbitrary nodes can be inserted for purposes of QC and visualization. By default, this pipeline is empty (it has no effect). + +![4_view_group.png](../../images/rv-reference-manual-4-rv-cxx-e-rv4-view-group-03.png) + +Figure 2.3:View Group Internals + +| Command | Mu Return Type | Python Return Type | Description | +| --- | --- | --- | --- | +| setViewNode (string *nodename* ) | void | None | Connect the specified node to the view group | +| nextViewNode () | void | None | Switch to next view in the view history (if there is one) | +| prevViewNode () | void | None | Switch to next previous in the view history (if there is one) | + +Table 2.3:High level commands used to change the view group inputs + +### 2.5 Sequence Group Node + + +The sequence group node causes its inputs to be rendered one after another in time.The internal RVSequence node contains an EDL data structure which determines the order and possibly the frame ranges for its inputs. By default the EDL is automatically created by sequencing the inputs in order from the first to last with their full frame ranges. The automatic EDL function can be turned off in which case arbitrary EDL data can be set including cuts back to a single source multiple times.Each input to a sequence group has a unique sub-graph associated with it that includes an RVPaint node to hold annotation per input and an optional retime node to force all input media to the same FPS. + +![5_sequence_group.png](../../images/rv-reference-manual-5-rv-cxx-rv4-sequence-group-04.png) + +Figure 2.4:Sequence Group Internals + +### 2.6 Stack Group Node + + +The stack group node displays its inputs on top of each other and can control a crop per input in order to allow pixels from lower layers to be seen under upper layers. Similar to a sequence group, the stack group contains an optional retime node per input in order to force all of the input FPS' to the same value.Unlike the sequence group, the stack group's paint node stores annotation after the stacking so it always appears on top of all images. + +![6_stack_group.png](../../images/rv-reference-manual-6-rv-cxx-rv4-stack-group-05.png) + +Figure 2.5:Stack Group Internals + +### 2.7 Layout Group Node + + +The layout group is similar to a stack group, but instead of showing all of its inputs on top of one another, the inputs are transformed into a grid, row, column, or under control of the user (manually). Like the other group nodes, there is an optional retime node to force all inputs to a common FPS. Annotations on the layout group appear on top of all images regardless of their input order. + +![7_layout_group.png](../../images/rv-reference-manual-7-rv-cxx-rv4-layout-group-06.png) + +Figure 2.6:Layout Group Internals + +### 2.8 Display Group Node + + +There is one display group for each video device accessible to RV. For example in the case of a dual monitor setup, there would be two display groups: one for each monitor. The display group has two functions: to prepare the working space pixels for display on the associated device and to set any stereo modes for that device.By default the display group's pipeline uses an RVDisplayColor node to provide the color correction. The user can use any node for that purpose instead or in addition to the existing RVDisplayColor. For example, when OpenColorIO is being used a DisplayOCIONode is used in place of the RVDisplayColor.For a given desktop setup with multiple monitors only one of the RVDisplayGroups is active at a time: the one corresponding to the monitor that RV's main window is on. In presentation mode, two RVDisplayGroups will be active: one for RV's main window and one for the presentation device. Each display group has properties which identify their associated device.Changes to a display group affect the color and stereo mode for the associated device only. In order to make a global color change that affects all devices, a node should be inserted into the view group's pipeline or earlier in the graph. + +![8_display_group.png](../../images/rv-reference-manual-8-rv-cxx-rv4-display-group-07.png) + +Figure 2.7:Display Group Internals + +### 2.9 Addressing Properties + + +A full property name has three parts: the node name, the component name, and the property name. These are concatenated together with dots like *nodename.componentname.propertyname* . Each property has its own type which can be set and retrieved with one of the set or get functions. You must use the correct get or set function to access the property. For example, to set the display gamma, which is part of the \`\`display” node, you need to use setFloatProperty() like so in Mu: + +``` + setFloatProperty("display.color.gamma", float[] {2.2, 2.2, 2.2}, true) +``` +or in Python: + +``` + setFloatProperty("display.color.gamma", [2.2, 2.2, 2.2], True) +``` +In this case the value is being set to 2.2. + +![9_prop_inactive.png](../../images/rv-reference-manual-9-rv-cxx-rv4-prop-inactive-08.png) + +Figure 2.8:Conceptual diagram of RV Image and Audio Processing Graph for a session with a single sequence of two sources. The default stack and layout are not included in this diagram, but would be present. + +In an RV session, some node names will vary per the source(s) being displayed and some will not. Figure [2.8](#rv-pipeline-small) shows a pipeline diagram for one possible configuration and indicates which are per-source (duplicated) and which are not.At any point in time, a subset of the graph is active. For example if you have three sources in a session and RV is in sequence mode, at any given frame only one source branch will be active. There is a second way to address nodes in RV: by their types. This is done by putting a hash (#) in front of the type name. Addressing by node type will affect all of the currently active nodes of the given type. For example, a property in the color node is exposure which can be addressed directly like this in Mu: + +``` + color.color.exposure +``` +or using the type name like this: + +``` + #RVColor.color.exposure +``` +When the “#” type name syntax is used, and you use one of the set or get functions on the property, only nodes that are currently active and which are the first reachable of the given type will be considered. So in this case, if we were to set the exposure using type-addressing: + +``` + setFloatProperty("#RVColor.color.exposure", float[] {2.0, 2.0, 2.0}, true) +``` +or in Python: + +``` + setFloatProperty("#RVColor.color.exposure", [2.0, 2.0, 2.0], True) +``` +In sequence mode (i.e. the default case), only one RVColor node is usually active at a time (the one belonging to the source being viewed at the current frame). In stack mode, the RVColor nodes for all of the sources could be active. In that case, they will all have their exposure set. In the UI, properties are almost exclusively addressed in this manner so that making changes affects the currently visible sources only. See figure [2.9](#active-nodes-in-the-image-processing-graph) for a diagrammatic explanation. + +![10_prop_active.png](../../images/rv-reference-manual-10-rv-cx-rv4-prop-active-09.png) + +Figure 2.9: Active Nodes in the Image Processing Graph + +The active nodes are those nodes which contribute to the rendered view at any given frame. In this configuration, when the sequence is active, there is only one source branch active (the yellow nodes). By addressing properties using their node's type name, you can affect only active nodes with that type without needing search for the exact node(s).There is an additional shorthand using “@” in front of a type name: + +``` + @RVDisplayColor.color.brightness +``` +The above would affect only the first RVDisplayColor node it finds instead of all RVDisplayColor nodes of depth 1 like “#” does. This is useful with presentation mode for example because setting the brightness would be confined to the first RVDisplayColor node which would be the one associated with the presentation device. If “#” was used, all devices would have their brightness modified. The utility of the “@” syntax is limited compared to “#” so if you are unsure of which to use try “#” first.Chapter [16](rv-reference-manual-chapter-sixteen.md#chapter-16-node-reference) has all the details about each node type. + +### 2.10 User Defined Properties + + +It's possible to add your own properties when creating an RV file from scratch or from the user interface code using the newProperty() function.Why would you want to do this? There are a few reasons to add a user defined property: + +1. You wish to save something in a session file that was created interactively by the user. +2. You're generating session files from outside RV and you want to include additional information (e.g. production tracking, annotations) which you'd like to have available when RV plays the session file. + +Some of the packages that come with RV show how to implement functionality for the above. + +### 2.11 Getting Information From Images + + +RV's UI often needs to take actions that depend on the context. Usually the context is the current image being displayed. Table [2.4](#command-functions-for-querying-displayed-images) shows the most useful command functions for getting information about displayed images. + +| Command | Description | +| --- | --- | +| sourceAtPixel | Given a point in the view, returns a structure with information about the source(s) underneath the point. | +| sourcesRendered | Returns information about all sources rendered in the current view (even those that may have been culled). | +| sourceLayers | Given the name of a source, returns the layers in the source. | +| sourceGeometry | Given the name of a source, returns the geometry (bounding box) of that source. | +| sourceMedia | Given the name of a source, returns the a list of its media files. | +| sourcePixelValue | Given the name of a source and a coordinate in the image, returns an RGBA pixel value at that coordinate. This function may convert chroma image pixels to Rec709 primary RGB in the process. | +| sourceAttributes | Given the name of a source and optionally the name a particular media file in the source, returns an array of tuples which contain attribute names and values. | +| sourceStructure | Given the name of a source and optionally the name a particular media file in the source, returns information about image size, bit depth, number of channels, underlying data type, and number of planes in the image. | +| sourceDisplayChannelNames | Given the name of a source, returns an array of channel names current being displayed. | + +Table 2.4: Command Functions for Querying Displayed Images + +For example, when automating color management, the color space of the image or the origin of the image may be required to determine the best way to view the image (e.g., for a certain kind of DPX file you might want to use a particular display or file LUT). The color space is often stored as image attribute. In some cases, image attributes are misleading–for example, a well known 3D software package renders images with incorrect information about pixel aspect ratio—usually other information in the image attributes coupled with the file name and origin are enough to make a good guess. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual.md b/doc/rv-manuals/rv-user-manual.md new file mode 100644 index 000000000..96e3e8195 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual.md @@ -0,0 +1,31 @@ +# Open RV User Manual + +* [Chapter 1 - Introduction](rv-user-manual/rv-user-manual-chapter-one.md) +* [Chapter 2 - Installation](rv-user-manual/rv-user-manual-chapter-two.md) +* [Chapter 3 - Command Line Usage](rv-user-manual/rv-user-manual-chapter-three.md) +* [Chapter 4 - User Interface](rv-user-manual/rv-user-manual-chapter-four.md) +* [Chapter 5 - The Session and the Session Manager](rv-user-manual/rv-user-manual-chapter-five.md) +* [Chapter 6 - Presentation Mode and Video Devices](rv-user-manual/rv-user-manual-chapter-six.md) +* [Chapter 7 - How a Pixel Gets from a File to the Screen](rv-user-manual/rv-user-manual-chapter-seven.md) +* [Chapter 8 - Using LUTs in Open RV](rv-user-manual/rv-user-manual-chapter-eight.md) +* [Chapter 9 - Using CDLs in Open RV](rv-user-manual/rv-user-manual-chapter-nine.md) +* [Chapter 10 - Packages](rv-user-manual/rv-user-manual-chapter-ten.md) +* [Chapter 13 - Networking](rv-user-manual/rv-user-manual-chapter-thirteen.md) +* [Chapter 11 - OpenColorIO](rv-user-manual/rv-user-manual-chapter-eleven.md) +* [Chapter 12 - Stereo Viewing](rv-user-manual/rv-user-manual-chapter-twelve.md) +* [Chapter 14 - Maximizing Performance](rv-user-manual/rv-user-manual-chapter-fourteen.md) +* [Chapter 15 - File Formats](rv-user-manual/rv-user-manual-chapter-fifteen.md) +* [Chapter 16 - RVIO](rv-user-manual/rv-user-manual-chapter-sixteen.md) +* [Chapter 17 - RVLS](rv-user-manual/rv-user-manual-chapter-seventeen.md) +* [Chapter 18 - RVPUSH](rv-user-manual/rv-user-manual-chapter-eighteen.md) +* [Appendix A - Tuning Platform Audio for Linux](rv-user-manual/rv-user-manual-chapter-a.md) +* [Appendix B - Stereo Setup](rv-user-manual/rv-user-manual-chapter-b.md) +* [Appendix C - The RVLINK Protocol: Using Open RV as a URL Handler](rv-user-manual/rv-user-manual-chapter-c.md) +* [Appendix D - Using Open RV as Nuke's Flip Book Player](rv-user-manual/rv-user-manual-chapter-d.md) +* [Appendix E - Open RV Audio on Linux](rv-user-manual/rv-user-manual-chapter-e.md) +* [Appendix F - Troubleshooting Networking](rv-user-manual/rv-user-manual-chapter-f.md) +* [Appendix G - Rising Sun Research CineSpace .csp File Format](rv-user-manual/rv-user-manual-chapter-g.md) +* [Appendix H - Crash Reporting](rv-user-manual/rv-user-manual-chapter-h.md) +* [Appendix I - PySide example usage](rv-user-manual/rv-user-manual-chapter-i.md) +* [Appendix J - Supported Multichannel Audio Layouts](rv-user-manual/rv-user-manual-chapter-j.md) +* [Appendix K - Localizing media paths with RV_OS_PATH or RV_PATHSWAP](rv-user-manual/rv-user-manual-chapter-k.md) \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-a.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-a.md new file mode 100644 index 000000000..ce3ca6865 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-a.md @@ -0,0 +1,117 @@ +# A - Tuning Platform Audio for Linux + +On Linux, RV's Platform Audio module is based on Qt's QAudioOutput, which is implemented against ALSA's api. As such, we have added several environment variables that allow us to better debug audio issues and which also allows users to tune the audio data IO performance between RV and their chosen playback audio hardware device. Note these environment variables are only supported on Linux. + +The environment variables are as follows: + +| Environment Variable | Variable values | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| TWK_QTAUDIOOUTPUT_ENABLE_DEBUG | 0 = No debugging msgs (default)
1 = Standard debugging msgs ; displays the ALSA device hardware parameter values used to configure the audio device. It will also display errors like buffer underruns.
2 = Verbose debugging msgs ; use this value for tracing crashes. Messages are displayed with each audio period write to the audio alsa device. | +| TWK_QTAUDIOOUTPUT_BUFFER_TIME | value in microsecs e.g. 120000 for 120ms (default is 120000) | +| TWK_QTAUDIOOUTPUT_PERIOD_TIME | value in microsecs e.g. 20000 for 20ms (default is 20000)| + +Table K.1: + +Platform Audio Environment Variables + +``` +# setenv TWK_QTAUDIOOUTPUT_ENABLE_DEBUG 1 +# setenv RV_NO_CONSOLE_REDIRECT 1 +# rv myclip.mov + +Version 7.1.0, built on Aug 31 29 2016 at 03:05:11 (HEAD=166a76e). (L) +Copyright (c) 2016 Autodesk, Inc. All rights reserved. +INFO: myclip.mov +DEBUG: Number of available audio devices 3 +DEBUG: Audio device = "default" +DEBUG: Audio device actual = "pulse" +DEBUG: ranges: pmin= 666 , pmax= 7281792 , bmin= 2000 , bmax= 21845334 +DEBUG: used: buffer_frames= 5760 , period_frames= 960 # in sample count +DEBUG: used: buffer_size= 23040 , period_size= 3840 # in bytes +DEBUG: used: buffer_time= 120000 , period_time= 20000 # in microsecs +DEBUG: used: chunks= 6 # no of periods per buffer +DEBUG: used: max write periods/chunks=0 # 0 implies max possile. +DEBUG: used: bytesAvailable= 23040 # amount of free space in the audio buffer on device open(). +``` + +The environment variables TWK_QTAUDIOOUTPUT_BUFFER_TIME and TWK_QTAUDIOOUTPUT_PERIOD_TIME can be used to set the buffer size and period size of the audio device. The audio buffer size is always an integral number of period sizes, in other words chose values such that TWK_QTAUDIOOUTPUT_BUFFER_TIME = N \* TWK_QTAUDIOOUTPUT_PERIOD_TIME; where N is an integer. Experimentally we have found N = 6 produced a measured audio - video sync < 10ms; while increasing or decreasing N from this value seemed to produce larger and increasingly worst av sync lag numbers. + +It is worth remembering that TWK_QTAUDIOOUTPUT_BUFFER_TIME determines overall size of the audio buffer. The audio device will not start playing until the buffer is completely filled first. This means the buffer size can influence the lag at the beginning when play first starts. + +So for a given TWK_QTAUDIOOUTPUT_BUFFER_TIME, TWK_QTAUDIOOUTPUT_PERIOD_TIME determines the number of period buffers within the overall buffer size and this influences the average av sync value and if too small <=3 leads to buffer underun errors and crackles in audio. + +``` +# setenv TWK_QTAUDIOOUTPUT_ENABLE_DEBUG 1 +# setenv RV_NO_CONSOLE_REDIRECT 1 +# setenv TWK_QTAUDIOOUTPUT_BUFFER_TIME 60000 +# setenv TWK_QTAUDIOOUTPUT_PERIOD_TIME 20000 +# rv myclip.mov + +Version 7.1.0, built on Aug 31 29 2016 at 03:05:11 (HEAD=166a76e). (L) +Copyright (c) 2016 Autodesk, Inc. All rights reserved. +INFO: myclip.mov +DEBUG: Number of available audio devices 3 +DEBUG: Audio device = "default" +DEBUG: Audio device actual = "pulse" +DEBUG: ranges: pmin= 666 , pmax= 7281792 , bmin= 2000 , bmax= 21845334 +DEBUG: used: buffer_frames= 2880 , period_frames= 960 # in sample count +DEBUG: used: buffer_size= 11520 , period_size= 3840 # in bytes +DEBUG: used: buffer_time= 60000 , period_time= 20000 # in microsecs +DEBUG: used: chunks= 3 # no of periods per buffer +DEBUG: used: max write periods/chunks=0 # 0 implies max possile. +DEBUG: used: bytesAvailable= 11520 # amount of free space in the audio buffer on device open(). +DEBUG: *** Buffer underrun: -32 +DEBUG: *** Buffer underrun: -32 +DEBUG: *** Buffer underrun: -32 +DEBUG: *** Buffer underrun: -32 +DEBUG: *** Buffer underrun: -32 +``` +The tuning process steps: + +1. Launch RV and setup the File->Preferences->Audio tab settings as follows: + 1. Output Module: Platform Audio + 2. Output Device: Default + 3. Output Format: Stereo 32bit float 48000 + 4. Enable 'Keep Audio device open when playing' + 5. Turn off 'Hardware Audio/Video Synchronization' + 6. Enable 'Scrubbing on by default' +2. Determine the smallest TWK_QTAUDIOOUTPUT_BUFFER_TIME and TWK_QTAUDIOOUTPUT_PERIOD_TIME settings before buffer underruns occur. + 1. Set Platform Audio debugging messaging on... TWK_QTAUDIOOUTPUT_ENABLE_DEBUG = 1. + 2. Set values for TWK_QTAUDIOOUTPUT_BUFFER_TIME and TWK_QTAUDIOOUTPUT_PERIOD_TIME. + 3. Try the following combinations of TWK_QTAUDIOOUTPUT_BUFFER_TIME / TWK_QTAUDIOOUTPUT_PERIOD_TIME i.e. 60000 / 10000 and 36000 / 6000. + 4. Launch RV with a 48Khz video clip and enable lookahead/region caching. Either cache option would do as long as you size the video cache appropriately so the entire clip is cached. Playback and observe for buffer underruns messages. Note you should also stress test running multiple RVs with the same config. + 5. If no underun errors occur; repeat the previous step, setting smaller values for TWK_QTAUDIOOUTPUT_BUFFER_TIME and TWK_QTAUDIOOUTPUT_PERIOD_TIME keeping in mind that the ratio of TWK_QTAUDIOOUTPUT_BUFFER_TIME/TWK_QTAUDIOOUTPUT_PERIOD_TIME must be greater than 3 and ideally around 6. +3. Once you have found the smallest value of TWK_QTAUDIOOUTPUT_BUFFER_TIME; use an av sync meter to measure the average av sync lag playing back RV's movieproc syncflash clip. + 1. With TWK_QTAUDIOOUTPUT_BUFFER_TIME fixed to the value determined in the previous step; increase and decrease TWK_QTAUDIOOUTPUT_PERIOD_TIME, bearing in mind TWK_QTAUDIOOUTPUT_BUFFER_TIME must be a integer multiple of TWK_QTAUDIOOUTPUT_PERIOD_TIME. Find the multiple with the lowest measure average av sync values (i.e. the average of at least 25 av sync measurements). + +The table below provides the smallest values of TWK_QTAUDIOOUTPUT_BUFFER_TIME and TWK_QTAUDIOOUTPUT_PERIOD_TIME that prevents buffer underruns (i.e. audio corruption/static issues), minimizes the lag at play-start and average av sync. Note the default value for buffer time is 120000 and period time is 20000. + +| Hardware class | Audio Hardware | OS Machine Specs | BUFFER_TIME | PERIOD_TIME | Avrg AV sync | +| ---------------- | ------------------------------- | --------------------------------------------------------------------------------------------------- | ------------ | ------------ | ------------ | +| Gaming machine | onboard intel HDA/realtek 7.1ch | Centos7.2, Asus Rampage Gene IV, iCore7 3.5Ghz, 32GB 2400Mhz RAM, SSD, Quadro K2200 (nv drv 352.55) | 36000 | 6000 | ~3ms | +| HP workstation | onboard intel HDA/realtek 2ch | Centos6.6, HPZ820, Xeon 12core, 48GB RAM, SSD, Quadro K6000 (nv 352.63) | TDB | TDB | TDB | +| Dell workstation | onboard intel HDA/realtek 2ch | Centos7.2 | TDB | TDB | TDB | +| Any | USB Soundblaster XiFi | Centos7.2, Asus Rampage Gene IV, iCore7 3.5Ghz, 32GB 2400Mhz RAM, SSD, Quadro K2200 (nv drv 352.55) | 120000 | 20000 | TDB | +| | | | | |   | + +Table K.2: + +Some tuned configurations for 24fps on a 60Hz monitor. + +### K.1 Suggestions for resolving audio static issues with PulseAudio for Linux + + +In this Appendix, we outline some suggestions for configuring your linux distribution's pulseaudio to address the issue of audio static during playback when RV sees heavy and continuous use within the context of a production environment. While this issue is intermittent and hard to reproduce, it can occur with sufficient frequency to become a support burden. Please note the suggestions here should be validated by your systems/video engineering dept before you adopt them. + +Settings for /etc/pulse/default.pa. + +``` +# For RHEL7 equivalent systems +# +# fragments=2 (prevents problems on kvm and teradici host) +# fixed_latency_range=1 (fixes static problem when system is heavily loaded or in swap) +# +load-module module-alsa-card device_id=PCH format=s16le rate=48000 fragments=2 fixed_latency_range=1 +``` + +NB: Many thanks to JayHillard/ChrisMihaly@WDAS from sharing these settings with us. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-b.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-b.md new file mode 100644 index 000000000..05ae7ceba --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-b.md @@ -0,0 +1,68 @@ +# B - Stereo Setup + +### B.1 Linux + + +This is taken from an NVIDIA README file. The portions pertaining to stereo modes are reproduced here: + +The following driver options are supported by the NVIDIA X driver. They may be specified either in the Screen or Device sections of the X config file. + +#### Option "Stereo" "integer" + +Enable offering of quad-buffered stereo visuals on Quadro. Integer indicates the type of stereo glasses being used + +| Value | Equipment | +| --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | DDC glasses. The sync signal is sent to the glasses via the DDC signal to the monitor. These usually involve a passthrough cable between the monitor and video card | +| 2 | "Blueline" glasses. These usually involve a passthrough cable between the monitor and video card. The glasses know which eye to display based on the length of a blue line visible at the bottom of the screen. When in this mode, the root window dimensions are one pixel shorter in the Y dimension than requested. This mode does not work with virtual root window sizes larger than the visible root window size (desktop panning). | +| 3 | Onboard stereo support. This is usually only found on professional cards. The glasses connect via a DIN connector on the back of the video card. | +| 4 | TwinView clone mode stereo (aka "passive" stereo). On video cards that support TwinView, the left eye is displayed on the first display, and the right eye is displayed on the second display. This is normally used in conjuction with special projectors to produce 2 polarized images which are then viewed with polarized glasses. To use this stereo mode, you must also configure TwinView in clone mode with the same resolution, panning offset, and panning domains on each display. | + +Stereo is only available on Quadro cards. Stereo options 1, 2, and 3 (aka "active" stereo) may be used with TwinView if all modes within each metamode have identical timing values. Please see Appendix J for suggestions on making sure the modes within your metamodes are identical. The identical modeline requirement is not necessary for Stereo option 4 ("passive" stereo). Currently, stereo operation may be "quirky" on the original Quadro (NV10) chip and left-right flipping may be erratic. We are trying to resolve this issue for a future release. Default: Stereo is not enabled. + +UBB must be enabled when stereo is enabled (this is the default behavior). + +Stereo options 1, 2, and 3 (aka "active" stereo) are not supported on digital flat panels. + +#### Option "AllowDFPStereo" "boolean" + +By default, the NVIDIA X driver performs a check which turns off active stereo (stereo options 1, 2, and 3) if the X screen is driving a DFP. The "AllowDFPStereo" option bypasses this check. + + + +ENSURING IDENTICAL MODE TIMINGS + +Some functionality, such as Active Stereo with TwinView, requires control over exactly what mode timings are used. There are several ways to accomplish that: + +If you only want to make sure that both display devices use the same modes, you only need to make sure that both display devices use the same HorizSync and VertRefresh values when performing mode validation; this would be done by making sure the HorizSync and SecondMonitorHorizSync match, and that the VertRefresh and the SecondMonitorVertRefresh match. + +A more explicit approach is to specify the modeline you wish to use (using one of the modeline generators available), and using a unique name. For example, if you wanted to use 1024x768 at 120 Hz on each monitor in TwinView with active stereo, you might add something like: # 1024x768 @ 120.00 Hz (GTF) hsync: 98.76 kHz; pclk: 139.05 MHz Modeline "1024x768_120" 139.05 1024 1104 1216 1408 768 769 772 823 -HSync +Vsync In the monitor section of your X config file, and then in the Screen section of your X config file, specify a MetaMode like this: Option "MetaModes" "1024x768_120, 1024x768_120" + +#### Support for GLX in Xinerama + +This driver supports GLX when Xinerama is enabled on similar GPUs. The Xinerama extension takes multiple physical X screens (possibly spanning multiple GPUs), and binds them into one logical X screen. This allows windows to be dragged between GPUs and to span across multiple GPUs. The NVIDIA driver supports hardware accelerated OpenGL rendering across all NVIDIA GPUs when Xinerama is enabled. + +To configure Xinerama: configure multiple X screens (please refer to the XF86Config(5x) or xorg.conf(5x) manpages for details). The Xinerama extension can be enabled by adding the line + +Option "Xinerama" "True" + +to the "ServerFlags" section of your X config file. + +Requirements: + +It is recommended to use identical GPUs. Some combinations of non-identical, but similar, GPUs are supported. If a GPU is incompatible with the rest of a Xinerama desktop then no OpenGL rendering will appear on the screens driven by that GPU. Rendering will still appear normally on screens connected to other supported GPUs. In this situation the X log file will include a message of the form: + +(WW) NVIDIA(2): The GPU driving screen 2 is incompatible with the rest of (WW) NVIDIA(2): the GPUs composing the desktop. OpenGL rendering will (WW) NVIDIA(2): be turned off on screen 2. + +The NVIDIA X driver must be used for all X screens in the server. + +Only the intersection of capabilities across all GPUs will be advertised. + +X configuration options that affect GLX operation (e.g.: stereo, overlays) should be set consistently across all X screens in the X server. + +### B.2 macOS and Windows + + +There are no special requirements (other than having a proper GPU that can produce stereo output). If the macOS or Windows graphical environment can provide RV with a stereo GL context, it will play back in stereo. If not, the console widget will pop up and you will see GL errors. + +If you have trouble with the stereo on the Mac, you might have some luck on one of Apple's mailing lists. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-c.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-c.md new file mode 100644 index 000000000..4bd612dd9 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-c.md @@ -0,0 +1,122 @@ +# C - The RVLINK Protocol: Using Open RV as a URL Handler + +RV can act as a protocol handler for URLs using the "rvlink" protocol. These URLs have the form: + +``` +rvlink:// +``` + +for example, + +``` +rvlink:// -l -play /path/to/my/movie.mov +``` + +Will start RV (or on the mac, create a new session or replace the current session), load movie.mov, turn on the look-ahead cache, and start playback. + +### C.1 Using rvlink URLs + + +You can insert rvlinks into web pages, chat sessions, emails, etc. Of course it is up to each individual application whether it recognizes the protocol. Some applications can be taught to treat anything of the form \`\`name://” as a link to the name protocol, but others are hard-coded to only recognize \`\`http://”, \`\`ftp://”, etc. Some examples of apps that will recognize rvlinks are + +* Firefox +* Safari +* Chrome +* Internet Explorer +* Thunderbird +* Mac Mail +* IChat + +One example of an app that will only recognize a hard-coded set of protocol types is Pidgin. + +To use an rvlink in HTML, this kind of thing should work: + +``` +play movie +``` + +**Note** the quotation marks. + +In other settings (like pasting into an email, for example) you may want a \`\`web-encoded” URL, since an RV command line can contain arbitrary characters. RV will do the web-encoding for you, if you ask it to do so on the command line. For example, if you run: + +``` +rv -l -play /path/to/my/movie.mov -encodeURL +``` + +RV will print the encoded url to the terminal: + +``` +rvlink://%20-l%20-play%20%2Fpath%2Fto%2Fmy%2Fmovie.mov +``` + +Some browsers, however, like IE and Konqueror, seem to modify even encoded URLs before they get to the protocol handler. If the rvlink URL contains interesting characters, even an encoded URL will not work with these browsers. To address this issue, RV also supports *fully-baked* URLs, that look like this: + +rvlink://baked/202d6c202d706c6179202f706174682f746f2f6d792f6d6f7669652e6d6f76 + +This form of URL has the disadvantage of being totally illegible to humans, but as a last resort, it should ensure that the URL reaches the protocol handler without interference. As with encoded URLs, baked URLs can be generated from command-lines by giving RV the "-bakeURL" command-line option. Note that the "baked" URL is just a hex-encoded version of the command line, so in addition to using RV itself, you can do it programmatically. For example, in python, something like + +``` +"-play /path/to/file.exr".encode("hex") +``` + +should do the trick + +#### A Note on Spaces + +In general, RV will treat spaces within the URL as delimiting arguments on the command line. If you want to include an argument with spaces (a movie name containing a space, for example) that argument must be enclosed in single quotes ('). For example, if the name of your media is "my movie.mov", the encoded rvlink URL to play it would look like: + +``` +rvlink://%20'my%20movie.mov' +``` + +### C.2 Installing the Protocol Handler + + +RV itself is the program that handles the rvlink protocol, so all that is necessary is to register RV as the designated rvlink handler with the OS or desktop environment. This is a different process on each of the platforms that RV supports. + +#### Windows + +On windows the rvlink protocol needs to be added to the registry. If you are using the RV installer for Windows this will happen automatically. If not, you need to edit the "rvlink.reg" file in the "etc" directory of the install to point at the install location, then just double click on this file to edit the registry. + +#### Mac + +Run RV once with the \`\`-registerHandler” command-line option in order to register that executable as the default rvlink protocol handler (this is to prevent confusion between RV and RV64 when both are installed). + +#### Linux + +Unlike Windows and Mac, Linux protocols are registered at the desktop environment level, not the OS level. After you've installed RV on your machine, you can run the "rv.install_handler" script in the install's bin directory. This script will register RV with both the KDE and Gnome desktop environments. + +Some application-specific notes: + +**Firefox** may or may not respect the gnome settings, in general, I've found that if there is enough of the gnome environment installed that gconfd is running (even if you're using KDE or some other desktop env), Firefox will pick up the gnome settings. If you can't get this to work, you can register the rvlink protocol with Firefox directly. + +**Konqueror** sadly seems to munge URLs before giving them to the protocol handler. For example by swapping upper for lowercase letters. And sometimes it does not pass them on at all. This means some rvlink URLs will work and some won't, so we recommend only "baked" rvlink urls with Konqueror at the moment. + +**Chrome** uses the underlying system defaults to handle protocols. In most cases this means whatever "xdg-open" is configured to use. Running the rv.install_handler should be sufficient + +### C.3 Custom Environment Variables + +Depending on the browser and desktop environment, **environment variables** set in a user environment may not be available to RV when started from a URL. If RV in your setup requires these **environment variables** (RV_SUPPORT_PATH, for example), it may have problems or not run at all when started from a URL. In order to ensure a consistent environment, you must ensure that these **environment variables** are set at a system-wide (or at least user-independent) level. On Linux, setting this up varies from distribution to distribution. You will want to research the appropriate steps for your distribution. On Windows, the usual **environment variable** techniques should work. MacOS has lately made this harder, but if you set the **environment variables** in /etc/launchd.conf (and reboot after setting), then the values should be picked up by all processes on the system. + +### C.4 Testing the Protocol Handler + +Once RV is properly configured as your rvlink: protocol handler, copy and paste the following URL into your browser window: `rvlink://smptebars.movieproc`. This should launch RV and display a standard SMPTE colorbar image. + +You can also test RV's "one-click sync" with these links ( **only** on linux and windows). + +* First copy and paste the following link into your browser to start your "sync target" RV (make sure no other RVs are running before you paste this link into your browser): `rvlink:// -reuse 0 -networkPort 45128 -network smptebars,start=1,end=100,fps=24.movieproc`. +* Then copy and paste the following link into your browser to sync with the previously started sync target: `rvlink:// -networkPort 45129 -network -networkConnect 127.0.0.1 45128 -flags syncPullFirst`. + +As of RV version 3.8.6, a new "fully baked" URL format will be supported by RV. This form of URL will be much more resistant to munging by evil browsers. Here's an example of such a baked URL: `rvlink://baked/20736d707465626172732e6d6f76696570726f63202d6576616c20277072696e74282532326e6f2070726f626c656d2535436e253232293b27`. + +### C.5 One-click sync + +The rvlink protocol allows you to build URLs that start and run RV. You can also set up a network connection to a remote RV from the command line and hence from an rvlink URL. Furthermore, you can direct that after the network connection is established, Sync should be activated, and the remote session information should be pulled over the network so that you’ll both be looking at the same media. + +To create such a link, use **Edit > Copy Sync Session URL**. It copies the link to the clipboard so you can share it with others. + +Imagine this scenario: You’re using RV, have some media loaded, when you decide you want your friend to look at it with you. They're in the next building, so rather than walk over there you want to start a RV Sync session with them.  + +To get connected, you can send them by email the URL from **Copy Sync Session URL**. + +So they can click the link, or copy paste it in a browser, to open in RV the linked session. When they click on this link, RV will start on their machine, connect to your running RV, pull the session information (so your media will match), and start Sync. So there you have it: One-Click Sync! diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-d.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-d.md new file mode 100644 index 000000000..aa03d9a8a --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-d.md @@ -0,0 +1,66 @@ +# D - Using Open RV as Nuke's Flip Book Player + +Nuke can easily be configured to use RV as a flipbook. Nuke can also be set up to render out OpenEXR temp files instead of the default rgb files. + +### D.1 Setting up a custom plugins area for Nuke + + +In order to configure Nuke to work with RV, you should set up a custom scripts/plugins directory where you can add new custom Nuke functionality without disturbing the default Nuke install. This directory can be anywhere on the NUKE_PATH environment variable, but note that the \`\`$HOME/.nuke'' directory is always on that NUKE_PATH, so we'll assume we're working there for now. + +1. In your $HOME/.nuke directory, create a file called \`\`init.py'', if it doesn't already exist, and also one called \`\`menu.py''. + +2. Add this line to the init.py file + +``` +nuke.pluginAddPath('./python') +``` + +3. Create the directory referenced by the line we just added + +``` +mkdir $HOME/.nuke/python +``` + +Done! Now Nuke well pick up new stuff from this area on start-up + +### D.2 Adding Open RV support in the custom plugins area + + +The following assumes that you've setup a custom plugin area as described in the previous section. + +1. Download the script \`\`rv_this.py'' from [this forum post](../../rv-packages/rv-nuke-integration.md). + +2. Move rv_this.py into $HOME/.nuke/python. + +3. Add the following line to the $HOME/.nuke/init.py file, **after** the pluginAddPath lines mentioned above: + +``` +import rv_this +``` + +4. Add the following to the $HOME/.nuke/menu.py file: + +``` +menubar = nuke.menu("Nuke"); +menubar.addCommand("File/Flipbook Selected in &RV", + "nukescripts.flipbook(rv_this.rv_this, nuke.selectedNode())", "#R") +``` + +RV will now appear in the render menu and will be available with the hot key Alt+r. + +To add an option to render your flipbook images in Open EXR, + +1. Find the file \`\`nukescripts/flip.py'' in your Nuke install and copy it to $HOME/.nuke/python/flipEXR.py. + +2. Edit $HOME/.nuke/python/flipEXR.py and find the two lines that specify the output file name (search for \`\`nuke_tmp_flip'') and change the file extension from \`\`rgb'' to \`\`exr''. + +3. Add the following line to the $HOME/.nuke/menu.py file: + +``` +menubar.addCommand("File/Flipbook Selected in &RV (EXR)", + "flipExr.flipbook(rv_this.rv_this, nuke.selectedNode())", "#E") +``` + +Now you'll have the option in the File menu to flipbook to EXR, with the hotkey Alt+e. + +You can edit the rv_this.py script to specify any rv options you wish to set as your viewing defaults. For example you could un-comment the script lines that will apply -gamma 2.2 or enable -sRGB, or you could specify a file or display LUT for RV to use. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-e.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-e.md new file mode 100644 index 000000000..28399955d --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-e.md @@ -0,0 +1,61 @@ +# E - Open RV Audio on Linux + +### E.1 Overview + +RV provides multiple audio modules so users can get the most audio functionality available given the constraints imposed by the Linux kernel version, the available audio frameworks, and the other audio applications in use. + +Potential user issues with audio on Linux are choppy playback (clicking, dead spots, drop-outs), or high latency (it takes a breath's worth of time to start playing). Both of these issues are extremely annoying and it is even worse when they both occur. Ideally latency is nearly 0 for high quality audio and drop-outs never occur. + +To make matters worse, there are two completely different audio drivers for Linux: OSS and ALSA. Only one of these can be present at a time (but both provide a \`\`compatibility'' API for the other). ALSA is the official Linux audio API and is shipped with almost all distributions. OSS is a cross platform API (for UNIX based machines). + +The Linux desktop projects, KDE and Gnome, both have sound servers build on top of OSS/ALSA. These are called esd and arts. Both of them have become deprecated in recent releases. If either of these processes is running you may find it difficult to use audio with RV (or other commercial products). + +Some distributions use a newer desktop sound server called PulseAudio . PulseAudio ships with most distributions and is on by default in RHEL, CentOS, and Fedora, and comes with Debian and Ubuntu. RV can use PulseAudio through the ALSA (Safe) module, however we strongly recommend against using PulseAudio. Though the PulseAudio server is designed to allow multiple applications to simultaneously play audio (and can run in real-time mode), we have found a decrease in stability with each new release of the module. Even for simple read-only API calls that query the state of the device. If possible for your workflow we suggest you remove PulseAudio, if it is not otherwise necessary. + +#### E.1.1 How Open RV Handles Linux Audio + +RV has two ALSA audio back ends for Linux: the old and safe versions. The old version is meant to run on distributions which shipped with ALSA versions 1.0.13 or earlier and which do not have PulseAudio installed. The safe version should work well with PulseAudio systems. Unless you know that your system is using PulseAudio you should try the ALSA Old back end to start with. + +On some systems, RV will fail to load one or more of its audio modules. This can occur of the API used by the module is newer than the one installed on the system. For example neither CentOS 4.6 or Fedora Core 4 can load the ALSA safe module unless the ALSA client library is upgraded. Any Linux system which uses ALSA 1.0 or newer should function with the ALSA old back end. + +### E.2 ALSA (Pre-1.0.14) + +The ALSA (Old) audio back uses a subset of the ALSA API. Without modification, it will make hardware devices directly accessible. (In ALSA terms these are the hw:x,y devices). You can also add to the list of devices using an environment variable called RV_ALSA_EXTRA_DEVICES. The syntax is: + +``` +visiblename1@alsadevicename1|visiblename2@alsadevicename2|... +``` + +So for example to add the “dmix” device, set the variable to: dmix@dmix. + +See the ALSA documentation for the .asoundrc file for more information about devices and how they can be created. Devices defined in the configuration files will not automatically show up in RV; you'll need to add them to the environment variable. + +When using the hardware devices other programs will not have access to the audio. + +### E.3 ALSA (Safe) + +The term “Safe” here refers to the subset of the ALSA API deemed safe to use in the presence of PulseAudio. Programs that use features that PulseAudio cannot intercept will cause inconsistent audio availability or unstable output. Use of hardware devices in the presence of PulseAudio can prevent other applications from using the audio driver whether they be ALSA or OSS based. + +The ALSA (Safe) audio back end uses a more modern version of the ALSA client API. If the version of ALSA client library on your system is newer than 1.0.13 this back end might work better on your system. PulseAudio system should definitely be using this audio module. + +The ALSA safe back end can use the RV_ALSA_EXTRA_DEVICES variable (see above). However, user/system defined devices will typically show up in the device list automatically. + +The safe back end does not give you direct access to the hardware devices by default. Typically you will see devices which look like: + +The xxxx and n values correspond to the hardware device values used in the ALSA old back end. You can force the use of the hardware devices by adding them to the environment variable. + +``` +front:CARD=xxxx,DEV=n +``` + +For example, the first device on the first card would be hw:0,0 in ALSA parlance. So setting the value of RV_ALSA_EXTRA_DEVICES to: + +First Hardware Device@hw:0,0 + +would add that to the list. + +We recommend that you do not add hardware devices when using the safe ALSA module. The hardware devices will typically be accessible via the default device list under different names (like front:CARD=xxxx,DEV=n from above). Unlike the default list of devices, the hardware devices will shut out other software from using the audio even on systems using the PulseAudio sound server. + +### E.4 Platform Audio + +RV supports a cross platform audio module based on Qt audio (which on Linux is ALSA based). Note Platform Audio supports playback on multichannel audio devices. diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-eight.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-eight.md new file mode 100644 index 000000000..fe31f9dfc --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-eight.md @@ -0,0 +1,101 @@ +# Chapter 8 - Using LUTs in Open RV + +Look up tables (LUTs) are useful for approximating complicated color transforms, especially those which have no known precise mathematical representation. RV provides four points in its color pipeline where LUTs can be applied: just after reading the file and before caching (pre-cache LUT [8.3](#83-the-pre-cache-lut) ) directly after the cache (file LUT), just before display transforms (look LUT), and as one of the display transforms (display LUT). The first three are per-source while there is only a single display LUT for each RV session. See [7.1](rv-user-manual-chapter-seven.md#rv-pixel-pipeline) + +Each of the LUTs can be either a channel LUT or a 3D LUT (the difference is explained below [8.1](#81-channel-1d-versus-3d-luts) . In the case of a 3D LUT there can also be an additional channel pre-LUT which can be used to shape the data. Both types of LUT are preceded by an input matrix which can scale high dynamic range data into the range of the LUT input (which is the range [0,1]). The values the LUT produces can be outside of the [0,1] range. This makes it possible for any of the LUTs to transform colors outside of the typical [0,1] range on both input and output. + +Internally, RV will store the LUT as either half precision floating point or 16 bit integral. Not all hardware is capable of processing LUTs stored as floating point (esp. the 3D LUTs) so if you notice banding or noisy output when using floating point LUT storage, you may have better luck with the 16 bit integral representation. If RV can determine whether the floating point LUTs are usable itself it will default to whatever is appropriate. + +When applied in hardware, the LUTs are interpolated when a value is not exactly represented in the LUT. This is usually more of an issue with 3D LUTs than channel LUTs since they have fewer samples per dimension. When interpolating between sample values, RV uses linear interpolation for channel LUTs and tri-linear interpolation for 3D LUTs. + +There are a number of ways to create a LUT. For film look simulation, it's often necessary to have special hardware to measure and compare film recorder output. Alternately, you use a lightbox; and assuming you have a well calibrated neutral monitor, \`\`eyeball'' the LUT by comparing the film to the monitor. + +RV has two different algorithms for applying the LUTs on the GPU: using floating point or fixed-point integer textures. Not all cards are equally capable with 3D LUTs and floating point. If RV detects that the card probably can't do a good job with the floating point hardware it will switch to a fixed-point representation using 16 bit integer LUTs. Sometimes even though the driver reports that the LUTs can be floating point, you will see banding in the final images. If that occurs, try forcing the use fixed-point LUTs by turning off the Floating Point 3D LUTs item on the Rendering tab of the preferences. The fixed point LUT algorithm will perform just as well as floating point in 99% of normal use cases. + +**To assign a LUT file to the source or Display:** + +* The Import menu under the File menu is used to assign LUT files to either the source’s Look or File pipelines. Additionally, LUT files can be assigned to the Pre-Cache and Display. + +### 8.1 Channel (1D) versus 3D LUTs + + +A channel LUT (also called a 1D LUT) has three independent look-up tables: one each for the R G and B channels. The alpha channel is not affected by the channel LUT. Channel LUTs may be very high resolution with up to 4096 samples. Each entry in the channel LUT maps an input channel value to an output channel value. The input values are in the [0,1] range, but the output values are unbounded. + +Channel LUTs differ from 3D LUTs in one critical way: they can only modify channel values independently of one another. In other words, e.g., the output value of the red channel can only be a result of the incoming red value. In a 3D LUT, this is not the case: the output value of the red channel can be dependent on any or all of the input red, green, and blue values. This is sometimes called channel cross-talk. + +The other important difference between channel and 3D LUTs is the number of samples. Channel LUTs are one dimensional and therefor consume much less memory than 3D LUTs. Because of this, channel LUTs can have more samples per-channel than 3D LUTs. + +The implication of all this is that channel LUTs are useful for representing functions like gamma or log to linear which don't involve cross-talk between channels whereas 3D LUTs are good for representing more general color transforms and simulating physical output like film. + +3D LUTs can be very memory intensive. A 64 × 64 × 64 LUT requires 64 3 × 4 bytes of data (3Mb). You can quickly run out of memory for your image on the graphics card by making the 3D LUT too big (e.g. 128 × 128 × 128 , this will slow RV down). RV does not require the 3D LUT to have the same resolution in each dimension. You may find that a particular LUT is smooth or nearly linear in one or more dimensions. In that case you can use a lower resolution in those dimensions. + +Some graphics cards have resolution issues with 3D textures which can cause loss of precision when RV's 3D LUT feature is enabled. On older NVidia cards and ATI cards in general, 3D textures may be limited to non-floating point color representations. Precision loss when using a LUT can be exacerbated by applying display gamma on these cards. To minimize precision loss on those types of cards, bake monitor gamma and/log-lin conversion directly into the display LUT. With newer GPUs this is not as much of an issue. + +### 8.2 Input Matrix and Pre-LUT + + +For HDR applications, the incoming data needs to be rescaled and possibly shaped. RV has two separate components which do this: the LUT input matrix and a channel pre-LUT. The input matrix is a general 4 × 4 matrix. For HDR pixels, the matrix is used to scale the incoming pixel to range [0 , 1] . The pre-LUT, the channel LUT, and the 3D LUT all take inputs in that range. Figure [8.1](#3d-and-channel-lut-components) shows a diagram of the channel and 3D LUT components and their input and output ranges. + +The pre-LUT is identical to the channel LUT in implementation. It maps single channel values to new values. Unlike the general channel LUT, the pre-LUT must always map values in the [0 , 1] range into the same range. The purpose of the pre-LUT is to condition the data before it's transformed by the 3D LUT. + +For example, it may make sense for 3D LUT input values to be in a non-linear space – like log space. If the incoming pixels are linear they need to be transformed to log before the 3D LUT is applied. By using a relatively high resolution pre-LUT the data can be transformed into that space without precision loss. + +![50_lut_pipeline_diagram.png](../../images/rv-user-manual-50-rv-cx-lut-pipeline-diagram-49.png) + +Figure 8.1: 3D and Channel LUT Components + +### 8.3 The Pre-Cache LUT + + +The first LUT that the pixels can be transformed by is the pre-cache LUT. This LUT has the same parameters and features as the other LUTs, but it is applied before the cache. The pre-cache LUT is currently applied by the CPU (not on the GPU) whereas the file, look, and display LUTs are all used by the graphics hardware directly. For this reason the pre-cache LUT is slightly slower than the others. + +The pre-cache LUT is useful when a special caching format is desired. For example by using the pre-cache LUT and the color bit depth formatting, you can have RV convert linear OpenEXR data into 8 bit integer format in log space. By using RV's log to linear conversion on the cached 8 bit data you can effectively store high dynamic range data (albeit limited range) and get double the number of frames into the cache. Many encoding schemes are possible by coupling a custom pre-cache LUT, change of bit depth, and the hardware file LUT to decode on the card. + +### 8.4 LUT File Formats + + +| Extension | Type | 1D | 3D | PreLUT | Float | Input | Output | +| --------- | ------------------- | -- | -- | ------ | ----- | ------------ | ------------------ | +| csp | Rising Sun Research | • | • | • | • | [ - ∞ , ∞] | [ - ∞ , ∞] | +| rv3dlut | RV 3D | | • | | • | [0 , 1] | [ - ∞ , ∞] | +| rvchlut | RV Channel | • | | | • | [0 , 1] | [ - ∞ , ∞] | +| 3dl | Lustre | | • | | | [0 , 1] | [0 , 1] | +| cube | IRIDAS | | • | | • | [0 , 1] | [ - ∞ , ∞] | +| any | Shake | • | | | • | [0 , 1] | [ - ∞ , ∞] | + +Table 8.1: LUT Formats (as Supported in RV) + +RV supports several of the common LUT file formats (See Table [8.1](#lut-formats-as-supported-in-rv) ). Unfortunately, not all LUT formats are equally capable and some of them are not terribly well defined. In most cases, you need to know the intended use of a particular LUT file. For example, it doesn't make sense to apply a LUT file which expects the incoming pixels to be in Kodak Log space to pixels from an EXR file (which is typically in a linear space). Often there is no way to tell the intended usage of a LUT file other than its file name or possibly comments in the file itself. Most formats do not have a public mechanism to indicate the usage to an application. + +To complicate matters, many LUT files are intended to map directly from the pixels in a particular file format directly to your monitor. When using these types of LUTs in RV you should be aware than making any changes to the color using RV's color corrections or display corrections will not produce expected results (because you are operation on pixels in the color space appropriate for the display, rather than in linear space). + +One of the more common types of LUT files you are likely to come across is one which maps Kodak Log space to sRGB display space. The file name of that kind of LUT might be log2sRGB or something similar. A variation on that same type of LUT might include an additional component that simulates the look of the pixels when projected from a particular type of film stock. Strictly speaking, you do not need to use log to sRGB LUTs with RV because it implements these functions itself (and they are exact, not approximated). So ideally, if you require film output simulation you have a LUT which only does that one transform. Of course this is often not the case; the world of LUT formats is a complicated one. + +#### 8.4.1 RSR .csp LUT Format + +Currently, the best LUT format for use with RV is the .csp format. This format handles high dynamic range input and output as well as non-linear and linear pre-LUTs. It maps most closely to RV's internal LUT functions. + +There is one type of .csp file which RV does not handle: a channel LUT with a non-linear pre-LUT. This is probably a very rare beast since an equivalent 1D LUT can be created with a linear pre-LUT. An error will occur if you attempt to use a channel LUT with a non-linear pre-LUT. + +When RV reads a pre-LUT from this file format and it can determine that the pre-LUT is linear, it will convert the pre-LUT into a matrix and apply it as the LUT input matrix. In that case the non-linear channel pre-LUT is not needed. If the pre-LUT is non-linear (in any channel) RV will construct a channel LUT which is used just before the 3D LUT. Input values in the .csp pre-LUT are normalized and the scaling is then moved to the input matrix. Using a matrix when possible frees up resources for other LUTs and images in the GPU. Any pre-LUT in a .csp file with only two values is by definition a linear pre-LUT. + +``` +CSPLUTV100 +3D + +2 ^\label{preLUTStart}^ +0 13.5 +0 1 +2 +0 13.5 +0 1 +2 +0 13.5 +0 1 ^\label{preLUTEnd}^ + +... +``` + +In the above listing, lines `preLUTStart` to `preLUTEnd` are linear pre-LUT values. In this case the pre-LUT values are mapping values int the range [0,13.5] down to [0,1] for processing by a 3D LUT (which is not shown). For a summary of the RSR .csp format see appendix [G](rv-user-manual-chapter-g.md#g-rising-sun-research-cinespace-csp-file-format). + +For the most part, it's not necessary to know the distinction between a linear and non-linear pre-LUT in the file. However, the behavior of the pre-LUT outside the bounds of its largest and smallest input values will be different for linear pre-LUTs. Since the pre-LUT is represented as a matrix, it will not clamp values outside the specified range. Non-linear pre-LUTs will clamp values. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-eighteen.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-eighteen.md new file mode 100644 index 000000000..d405b1c28 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-eighteen.md @@ -0,0 +1,57 @@ +# Chapter 18 - RVPUSH + +The RVPUSH command-line utility allows you to communicate with a running RV (you can designate a “target” RV with the -tag option). The help output from the command is as follows: + +``` +usage: rvpush [-tag ] + rvpush set + rvpush merge + rvpush mu-eval + rvpush mu-eval-return + rvpush py-eval + rvpush py-eval-return + rvpush py-exec + rvpush url rvlink:// + + Examples: + + To set the media contents of the currently running RV: + rvpush set [ foo.mov -in 101 -out 120 ] + + To add to the media contents of the currently running RV with tag "myrv": + rvpush -tag myrv merge [ fooLeft.mov fooRight.mov ] + + To execute arbitrary Mu code in the currently running RV: + rvpush mu-eval 'play()' + + To execute arbitrary Mu code in the currently running RV, and print the result: + rvpush mu-eval-return 'frame()' + + To evaluate an arbitrary Python expression in the currently running RV: + rvpush py-eval 'rv.commands.play()' + + To evaluate an arbitrary Python expression in the currently running RV, and print the result: + rvpush py-eval-return 'rv.commands.frame()' + + To execute arbitrary Python statements in the currently running RV: + rvpush py-exec 'from rv import commands; commands.play()' + + To process an rvlink url in the currently running RV, loading a movie into the current session: + rvpush url 'rvlink:// -reuse 1 foo.mov' + + To process an rvlink url in the currently running RV, loading a movie into a new session: + rvpush url 'rvlink:// -reuse 0 foo.mov' + + Set environment variable RVPUSH_RV_EXECUTABLE_PATH if you want rvpush to + start something other than the default RV when it cannot find a running + RV. Set to 'none' if you want no RV to be started. + + Exit status: + 4: Connection to running RV failed + 11: Could not connect to running RV, and could not start new RV + 15: Cound not connect to running RV, started new one. +``` + +Any number of media sources and associated per-source options can be specified for the set and merge commands. For the mu-eval command, it's probably best to put all your Mu code in a single quoted string. In the next release we'll have a python-eval command as well. + +If RVPUSH cannot find a running RV to talk to, it'll start one with the appropriate command-line options. Any later rvpush commands will use this RV until it exits. Note that the RV that RVPUSH starts will by default be the one in the same bin directory. If you'd rather start a different RV, or start a wrapper, etc, you can set the environment variable RVPUSH_RV_EXECUTABLE_PATH to point to the one you'd prefer. If you want RVPUSH to never start RV, you can set this environment variable to “none”. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-eleven.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-eleven.md new file mode 100644 index 000000000..a30b5ecf7 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-eleven.md @@ -0,0 +1,35 @@ +# Chapter 11 - OpenColorIO + +OpenColorIO (OCIO) is a software library which provides cross application color consistency. + +You can use OCIO in RV's display, look, viewing, linearize and color pipelines. In each case OCIO can be used to convert from an incoming and outgoing color space with a user defined OCIO context. OCIO requires some work to set up and should be considered an advanced feature. Large facilities may find OCIO particularly useful in RV when used in conjunction with Nuke, Mari, or other products which support it. + +You can learn about OpenColorIO at the [OpenColorIO website](http://www.opencolorio.org) . + +In order to use OCIO with RV a source setup package needs to be created along with OCIO configuration files, LUTs, and an appropriate user environment. RV comes with a package called ocio_source_setup which implements a default policy for using OCIO. We recommend the package be copied and modified to suit each facility that uses OCIO. + +There are four OCIO node types available in RV: OCIOLook, OCIOFile, OCIODispay, and the generic OCIO node. The default ocio_source_setup will use these nodes to replace RV's existing color, look, and display pipelines. + +RV uses the OCIO GPU path instead of the more common CPU path for color computation. There are slight differences between the two which you will need to be aware of. Specifically, the color allocation parameters in the config file can have a big effect on the quality of the OCIO LUTs generated by the library. + +The reference manual contains more detailed information about how to configure RV's OCIO nodes. + +### 11.1 The ocio_source_setup Package + + +OpenColorIO defines color spaces which are used to transform images for viewing or for storage in various file formats. It does not have any policy about when to apply these transforms. + +The provided ocio_source_setup package uses the default Sony Picture Imageworks method of detecting color space names in the incoming file names. The package queries OCIO and if the file name matches, the package will use OCIO nodes in various places in RV's pipelines instead of the usual RV color processing. If the incoming file name does not match an OCIO color space, the package will allow the existing RV color management to be used. + +One gotcha with this package is that once an OCIO managed file name has been detected, the package will use OCIO for the display correction as well. So mixed OCIO and RV managed imagery will always be viewed using OCIO's display color corrections. + +The package provides a basic menu fashioned after the OCIO display example program which allows you to file, look, and display transforms as well as forcing unrecognized media to use OCIO. + +In order to use the ocio_source_setup package you need to do the following: + +* Find the “OpenColorIO Basic Color Management” package in the Packages tab of the preferences. Make sure the “load” button next to the package name is activated and restart RV. +* Set the OCIO environment variable to the path to your config file. +* Start RV and load an image with the color space in the name. +* Note the "OCIO" top-level menu that appears when you use RV this way. You can use this menu to chose a Linearizing transform, or Display transform, from those provided by your config. + +The OpenColorIO mailing list and website is a good place to get help about config files, documentation, and general operation. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-f.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-f.md new file mode 100644 index 000000000..c6e663a54 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-f.md @@ -0,0 +1,17 @@ +# F - Troubleshooting Networking + +### Configure RV’s network settings + +You can use the Network dialog under the RV Menu to configure RV’s network syncing (as in between two or more instances of RV), set the name you show up as in sync mode on other RV’s, and establish the port you want to listen on for network connections. From this dialog you can initiate a connection to another and manage your Contacts list. + +The Contacts tab gives a list of users you have established connections with previously, but what is more important is the permission drop-down menu immediately below the contacts list. This drop-down allows you to set the default behavior for how to manage incoming connections. Once a user is added to the contacts list, the permission can be set per contact. Lastly, the Connections tab shows your active connections. + +At the bottom are two important buttons: “Connect…” and “Start Network.” “Connect…” lets you type in another RV’s network name and port. RV can only connect by hostname or IP address. It is important that the RV reaching out to connect can see the remote RV through any firewall. You may have to setup port forwarding or some other DMZ configuration for this to work. Please contact your network administrator for these types of advanced setups. + +Once you are satisfied with your settings, you can enable networking for RV by pressing the “Start Network” button. + +### Connections only work from one direction or are always refused + +Some operating systems have a firewall on by default that may be blocking the port RV is trying to use. When you start RV on the machine with the firewall and start networking it appears to be functioning correctly, but no one can connect to it. Check to see if the port that RV wants to use is available through the firewall. + +This is almost certainly the case when the connection works from one direction but not the other. The side which can make the connection is usually the one that has the firewall blocking RV (it won't let other machines in). \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-fifteen.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-fifteen.md new file mode 100644 index 000000000..25543d9cc --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-fifteen.md @@ -0,0 +1,316 @@ +# Chapter 15 - File Formats + +Each platform has a different set of file formats which RV can read by default. In addition, it's possible to download or purchase additional file format plug-ins which allow RV to read even more. This chapter is an overview of the most important formats and how RV uses them. + +You can have RV dump out all of the formats and codecs which it understands on the command like by giving it the -formats option. + +``` +shell> rv -formats +``` + +If you don't see a codec or container format in the list, then RV doesn't support it without installing one or more plug-ins. + +### 15.1 Movie File Formats + + +Movie files are single files which contain many images and often audio. These are often called *container file formats* because they usually specify how to store data, but not how it should be used. In most cases, that includes compression methods, play back algorithms, or even what the meaning of the data in the container is. + +Container file formats have additional sub-formats called *codecs* which determine things like compression and methods of play back. So even though a program like RV might be able to open a container file and look inside it, it might not understand one or more of the codecs which are being used in the container. In some cases the codec might be proprietary or meant to be used with a specific piece of software or hardware. + +#### 15.1.1 Stereo Movie Files + +Most movie file formats can have multiple tracks. When RV reads a movie file with two or more tracks it uses the first two tracks as the left and right eye when in stereo mode. You can create these files using the Apple QuickTime Player or RVIO on macOS and Windows or RVIO on Linux. + +#### 15.1.2 Text Tracks + +On Linux, RV will read the text track of a movie file if it exists and put the contents in an image attribute per frame. You can see this with the image info widget. Text tracks can be used to store metadata about the movie contents in a cross platform manner. + +#### 15.1.3 QuickTime Movie Files (.mov) + +RV uses a plugin which leverages ffmpeg directly to handle as many formats and codecs as possible. + +There are literally hundreds of codecs which can appear in a QuickTime file, but only some of them are useful in post-production. RV tries to support the most popular and useful ones. + +##### Photo-JPEG + +This codec has qualities which make it popular in film post production. Each frame is stored separately in the QuickTime file so moving to a random frame is fast. JPEG offers a number of ways to compress the image data including sub-sampling of color versus luminance and using sophisticated compression techniques. Color representation can be excellent when using Photo-JPEG. File sizes are moderate. + +The ffmpeg plugin does not respect all of the QuickTime atom objects. Usually this is a good thing as Apple's own library produces inconsistent results from platform to platform when displaying movie files with gama and colr atoms. The most notorious of the color atoms which typically affects the color of Photo-JPEG movies is the gama atom. The gama atom causes Apple's Quicktime to apply two gamma corrections to the image before it is displayed. RV will not do that and RVIO does not include the gama atom when writing Quicktime movies. + +##### Motion-JPEG + +Motion JPEG is similar to Photo-JPEG in that they both use the same compression algorithm. The main difference is that Motion-JPEG essentially stores a single frame in two parts: all of the even scanlines as a single JPEG image and all of the odd scanlines as a separate image. The codec interlaces the two parts together to form the final image. + +Motion-JPEG is supported on all platforms. + +##### H.264 (avc1) + +This is the latest and greatest codec which is usually associated with MPEG-4. H.264 stores keyframes and data which helps it generate in-between frames on the fly. Because it doesn't store every frame individually, H.264 compressed files can be much smaller than codecs like Photo-JPEG. The image quality for H.264 can be good depending on how the movie was created. If relatively simple creation software was used (like Apple's QuickTime player or RVIO) the results are usually OK, but not nearly as good as Photo or Motion-JPEG. + +##### RAW Codecs + +There are a number of *raw* codecs for QuickTime. Some of these store the pixel data as RGB, others as YUV. The Raw codecs tend to be very fast to read and play back. RV supports a number of raw codecs on all platforms. + +##### Audio + +RV supports most raw uncompressed audio codecs across platforms. + +RV and RVIO handle stereo audio. RV does not currently handle more than two channels of audio. + +#### 15.1.4 MPEG-4 Movie Files (.mp4) + +The MPEG-4 container file (.mp4) is almost identical to the QuickTime container file (.mov). The same codecs may be used to store data in either format. However, typically you find files encoded with H.264 or one of its predecessors. + +RV supports the MPEG-4 container on all platforms. + +#### 15.1.5 Windows AVI Files (.avi) + +RV supports AVI files with the same codecs as QuickTime on all platforms. + +#### 15.1.6 Windows Media Files (.wmv) + +There is no official support for this file format. + +#### 15.1.7 RV's movieproc Format (.movieproc) + +The movieproc "format” is not really a file format— all of the information about the pixels is encoded in the name of the file. So the file doesn't even need to exist on disk to use it. + +You might use a movieproc as a source if you need a procedural movie object like color bars or a hold on a black or other solid color frame in in a sequence. + +The syntax of the file name is a follows: + +TYPE, *OPTION=VALUE,OPTION=VALUE,OPTION=VALUE* .movieproc + +where TYPE is one of solid, smptebars, colorchart, noise, blank, black, white, grey/gray, hramp/hbramp, hwramp, or error, and *OPTION* and *VALUE* are one of those listed in [15.1](#movieproc-options) . A blank movieproc is one that will render no pixels but can occupy space in a sequence, and so can be used to form sequences with “holes”. + +| Option | Value | +| ---------------- | ---------------------------------------------------------------------- | +| start | frame number | +| end | frame number | +| fps | frames-per-second (e.g. 30 or 29.97) | +| inc | frame increment (default is 1) | +| red, green, blue | floating point value [-inf, inf] (used as one “side” for ramp types) | +| grey,gray | short hand for red=green=blue image | +| alpha | floating point value [0, 1] | +| width | Image width in pixels | +| height | Image height in pixels | +| depth | Channel Bit Depth: one of 8i, 16i, 16f, or 32f | +| interval | interval in seconds for “sync” and “flash” options below | +| audio | “sine” for continuous tone, or “sync” for once-per-interval chirp | +| freq | Audio Frequency (pitch), e.g. 440 for concert A | +| amp | Audio Amplitude [0,1] | +| rate | Audio Rate in Samples-per-second (e.g, 44100 for CD quality) | +| hpan | “animate” by shifting hpan pixels to the left each frame | +| flash | flash one frame every interval | +| filename | base64 string to spoof filename | +| attr:NAME | add an attribute named NAME to the Framebuffer with string value | + +Table 15.1: Movieproc Options + +For example the following will show color bars with a 1000Hz tone: + +``` +smptebars,audio=sine,freq=1000,start=1,end=30,fps=30.movieproc +``` + +and to make 100 black HD 1080 frames: + +``` +solid,start=1,end=100,fps=24,width=1920,height=1080.movieproc +``` + +or for an orange frame: + +``` +solid,red=1,green=.5,blue=0,width=1920,height=1080.movieproc +``` + +Anywhere you might use a normal file or sequence name in RV or RVIO you can use a movieproc instead. + +### 15.2 Image File Formats + +Each platform which RV runs on has its own selection of image formats. There are few important ones which are implemented across all platforms. Some of the most important formats are discussed in this chapter. + +#### 15.2.1 OpenEXR + +OpenEXR (EXR) is a high dynamic range floating point file format developed at ILM. It can store both 32 and 16 bit \`\`half'' floating point values with or without compression. RV supports the EXR half float type natively and when the GPU is capable, will render using type half type directly. RVIO is capable of converting to and from the half and full float formats. + +The EXR format is extremely flexible, capable of holding everything from multiple views (for stereo) to rendered layers like isolated diffuse and specular components as separate images. In addition it's possible to store subsampled chroma images or combinations of all the above. + +There have been at least two important revisions to the original OpenEXR specification, one the “multi-view” spec adds official support for top-level “view” objects that contain channels, and the most recent, the “multi-part” spec adjusts the file format so that groups of channels can be stored in discrete sections (“parts”) for efficient I/O. RV supports all varieties of EXR file supported by the latest specifications, but attempts to hide some of the resulting complexity from the user, as discussed below. + +##### Multiple Views + +Multiple view EXR files as defined by the Weta Multiview Extension are supported by RV. When in stereo mode, RV will look for views called \`\`left'' and \`\`right'' by default and can be programmatically told to use other channels (see the Reference Manual). + +You can also select one or more views in the UI to be loaded specifically when not in stereo mode. In stereo mode if you specify two views those will become the left and right. + +RV defaults to loading the default (or first) EXR layer from the view. The EXR views are independent of EXR layers which are described below — there can be multiple views each of which has multiple layers (or vice versa if you prefer to think of it that way). + +RV will recognize the file extensions \`\`exr”, \`\`sxr” (stereo exr), or \`\`openexr” as being OpenEXR files. Stereo views may be stored as either \`\`exr'' or \`\`sxr''; RV does not distinguish between the two extensions. + +##### Layers + +A layer in EXR terminology is a collection of channels which share a common channel name prefix separated by a dot character. Although EXR layers can have sub-layers and so on, in RV the layer hierarchy is flattened to a single level. So for example, an EXR channel in a traditional multi-view EXR file (multi-part files are discussed below), might have a channel called “left.keyLight.Diffuse.R”. In RV's simplified usage, the channel “R” is a member of the layer “keyLight.Diffuse”, which is a member of the view “left”. + +EXR layers are usually used to store components of images output by a renderer like Pixar's prman or Mental Image's Mental Ray. Often these layers are recombined in compositing software like Nuke which can handle the internal structure of the EXR file. This makes it easy for a compositor to control render output at a fine level to match it into a shot. Note that this not how EXR views are used — they are used for indicating stereo eyes (for examples) and each view may itself contain multiple EXR layers. + +RV initially will load the default layer (the one that has no name) or the first layer it finds. If this layer has R, G, B, A, Z, Y, RY, or BY channels, these will be assigned accordingly. If there are no obvious channel assignments to be made to the red, green, and blue channels, RV will take the first four channels it finds in the layer. If there are additional channels, you can assign these explicitly using the channel remap function in the UI under Image → Remap Source Image Channels. + +You can view alternate layers by selecting them in the Session Manager, or by selecting a layer on the command line. + +##### Y RY BY Images and Subsampling. + +By default, RV will read EXR files as planar images. Normally this distinction does not have any real effect at the user level, but in the case of Y RY BY images — which can be sub-sampled — it can have a big impact on playback performance. EXR has two special lossy compression schemes, B44 and B44A, which allow fast decode of high dynamic range imagery. B44 maintains a fixed size file regardless of the contents while B44A can potentially make smaller file sizes. As of OpenEXR v2.2.0, two further lossy DCT compression schemes were added i.e. DWAA (based on 32 scanlines) and DWAB (based on 256 scanlines). The level compression for DWAA and DWAB can be set through the '-quality ' option in rvio when writing out EXR files. This value is stored in the exr header (single/multipart) as a float attribute called 'dwaCompressionLevel' and defaults to 45 for DWAA and DWAB. + +When used with Y RY BY images with sub-sampled chroma (the BY and RY channels) RV will use the GPU to resample the chroma on the card resulting in faster throughput. When coupled with one or more multi-core CPUs, RV can get good direct from disk performance for these types of images while keeping the HDR information intact. At some resolutions, RV can even play back stereo HDR imagery in real-time from disk when used with the correct hardware. + +The OpenEXR file will only use B44 or B44A with half float images currently. 32 bit float images will not typically get as good performance. + + +##### Chromaticities + +EXR files may have chromaticities (primaries) included in the image attributes. If RV sees the **Color/Primaries** attribute, it will apply the corresponding transform to the Rec 709 primaries by default. You can turn off this behavior by selecting Color → Ignore File Primaries to turn off the transform. + +##### Channel Inheritance + +Some programs may assume channels should be “inherited” in EXR files. For example, a renderer may write a single alpha channel in the default layer and exclude redundant alpha channels from additional layers in the file like diffuse and specular. The idea is that these layers will share the default alpha during compositing. + +RV can attempt to aggregate channels assumed to be related like this by using either the command line option -exrInherit or by setting the flag in preferences (under the OpenEXR format). + +##### Data/Display Window Handling + +How an EXR image is displayed on screen, under certain data/display window overlap permutations, can vary across commercial and/or in-house viewing tools. To accommodate this and allow RV to emulate these differing behaviors, we provide several exr format preferences; see the table below for typical settings. + +The two OpenEXR preferences, found under Preferences-Format-OpenEXR, that define RV's data/display window handling behavior are -exrReadWindow and -exrReadWindowIsDisplayWindow. The flag -exrReadWindow specifies how the final data/display window (i.e ReadWindow) is defined. The choices are “Data Window”, “Display Window”, “Data Window Inside Display Window” and “Union of Data and Display Window”. In addition the flag -exrReadWindowIsDisplayWindow maybe optionally set to always make the ReadWindow the EXR display window; otherwise the display window is determined by source's EXR display window attributes. + +Table 15.3: + +OpenEXR format settings that emulate data/display window handling of various tools\* + +| Read Window | Read Window Is Display Window | Tool | +| --------------------------------- | --------------------------------- | ---------------------------- | +| Data Window | Checked | OSX Preview, Adobe Photoshop | +| Display Window | Not applicable | Nuke, exrdisplay | +| Data Window Inside Display Window | Not applicable | | +| Union of Data and Display Window | Checked | Renderman it, exrdisplay -w | + +\* = Subject to change by the vendor. + +Note: If you have EXR sequences in which each file has a possibly unique data/display window and caching the files in memory still results in slow play back compared to files without unique data/display windows and if you have "Use OpenGL Pixel Buffer Objects" ON in the rendering tab of the preferences, try turning it off. + +##### Parts + +As of OpenEXR version 2.0, EXR files can be written in separate sections, called “parts”. Each part has it's own header, and the underlying OpenEXR API can quickly skip to parts requested by the reader. Parts can contain layered and unlayered channels and be associated with views, but parts have names and so can also act as “layers” in the sense that unique parts can contain channels of the same name. + +In order to merge these possibly multiple and simultaneous uses of “parts” into the standard view/layer/channel hierarchy, RV subsumes EXR “parts” into the definition of “layer” when necessary. That is, as far as RV is concerned, a fully-specified channel name always looks like **view.layer.channel** , where (in the case of an EXR file) the three period-separated components of the name have these meanings: + +Table 15.4: Components of a fully-qualified channel name. + +| | | +| ----------- | --- | +| **view** | The name of the stereo view. This may be missing or “default” for “the default view”. The view name cannot contain a “.” Example: “left”, “right”, “center”, etc. +| **layer** | This component may include part names as well as traditional EXR layer names. The rule is that if there are no channel-name conflicts within a single view, the part names will be ignored (since they are not necessary to distinguish the channels), if there **is** a channel-name conflict, the part names maybe incorporated into the layer names. Sub-layers are also included. To be concrete, a channel called “Diffuse.R” stored in a part called “keyLight” is considered to be a member of a layer called “keyLight.Diffuse” if the channel “Diffuse.R” also appears in another part within the same view. | +| **channel** | The last component of a traditional EXR channel name, containing no “.” Example: “R”, “G”, “B”, “A”, “Z”, etc.| + +If an EXR has multiple parts and no channels are selected, RV will make a best effort to determine the most viable default channels. If multiple layers are similarly viable, RV will prefer a layer with RGB or RGBA channels from the first part (part 0). Unless explicitly directed, the channels chosen will not cross part boundaries unless Guess Channel Inheritance is enabled. + +Note: You can obtain uncached playback FPS speedups of between 2-3x (OSX/Linux/Windows) with multipart exr files over single part exr files for deep channeled images. + +#### 15.2.2 TIFF + +TIFF files come in many flavors some of which are rarely used. RV supports a useful subset of all possible TIFF files. This includes 32 bit floating point and multiple channels (beyond four) in both tiled and scanline. RV can read planar and interleaved TIFF files, but currently only reads the first image directory if there are more than one. + +RV will read all TIFF tags including EXIF tags and present them as image attributes. + +#### 15.2.3 DPX and Cineon + +RV and RVIO currently support 8 and16 bit, and the common 10 and 12 bit DPX and 10 bit Cineon files reading direct from disk on all platforms. The standard header fields are read and reported if they contain useful information (e.g., Motion Picture and TV fields). We do not currently support any of the vendor headers. + +RV supports the linear, log, and Rec. 709 transfer functions for DPX natively and others through the use of LUTs. + +RV decodes DPX and Cineon to 8 bit integer per channel by default. However, the reader can be configured from the command line or preferences to use 16 bit if needed. In addition the reader can use either planar or interleaved pixel formats. We have found that the combination of OS and hardware determines which format is fastest for playback, but we currently recommend RGB8_PLANAR on systems that can use OpenGL Pixel Buffer Objects (PBOs) and RGBA8 on systems that cannot. If you opt for 16 bit pixels, use the RGB16_PLANAR as the pixel format on PBO equipped hardware for maximum throughput. + +For best color fidelity use RV's built in Cineon Log → Linear option and sRGB display (or a particular display LUT if available). This option decodes the log space pixels directly to linear without interpolation inside a hardware shader and results in the full [0 , ∼ 13.5] range. + +RVIO decodes Cineon and DPX to 16 bit integer by default. + +The DPX and Cineon writers are both currently limited to 10 bit output. + +#### 15.2.4 IFF (ILBM) + +The IFF image format commonly created by Maya or Shake is supported by RV including the 32 bit float version. + +#### 15.2.5 JPEG + +RV can read JPEG natively as Y U V or R G B formats. The reader can collect any EXIF tags and pass them along as image attributes. The reader is limited to 8 bits per channel files. Like OpenEXR, DPX, and Cineon, JPEG there is a choice of I/O method with JPEG images. + + +#### 15.2.7 RAW DSLR Camera Formats + +There are a number of camera vendor specific formats which RV can read through RV's cross platform image plugin io_raw. This plugin uses the open src package LibRAW. The table below list some of the common raw formats that are read with this plugin. + +Table 15.7: + +Supported RAW formats + +| File Extension | File Format | +| ------------------ | ----------------------- | +| .arw | Sony/Minolta RAW | +| .cr2 | Canon RAW 2 | +| .crw | Canon RAW | +| .dng | Digital NeGative | +| .nef | Nikon Electronic Format | +| .orf | Olympus RAW | +| .pef | Pentax RAW | +| .ptx | Pentax RAW 2 | +| .raf | Fuji RAW | +| .rdc | Ricoh RAW | +| .rmf | Canon Raw Media Format | + +### 15.3 Audio File Formats + + +RV supports a number of basic uncompressed audio file formats across platforms. On Windows and macOS a number of compressed formats may be supported. Currently use of Microsoft wave files and Apple's AIFF formats are the best bet for cross platform use. RV does support multichannel audio files for playback to multichannel audio devices. (see Appendix [J](rv-user-manual-chapter-j.md#j-supported-multichannel-audio-layouts) ). + +### 15.4 Simple ASCII EDL Format + + +Each line of the ASCII file is either blank, a comment, or an edit event. A comment starts with a '#' character and continues until the end of a line. A comment can appear on the same line as an edit event. + +The format of an edit event is: + +``` +"SOURCE" START END +``` + +where SOURCE is a the path to a logical source movie such as: + +``` +"/some/path/to/foo.mov" +"/some/path/to/bar.1-100#.exr" +"/some/other_path/baz.1-100#.exr" +``` + +Note that the SOURCE name must always be in double quotes. If the path includes spaces, you do not need to use special escape characters to make sure they are accepted. So this: + +``` +"/some/place with spaces/foo.mov" +``` + +is OK. + +START and END are frame numbers in the movie. Note that START is the first frame to be include in the clip, and END is the last frame to be included (not, as in some edl formats, the frame **after** the last frame). For QuickTime .mov files or .avi files, the first frame of the file is frame 1. + +Here's an example .rvedl file: + +``` +# +# 4 source movies +# + +"/movies/foo.mov" 1 100 +"/movies/bar.mov" 20 50 +"/movies/render.1-100#.exr" 25 100 +"/movies/with a space.#.exr" 1 25 +``` \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-five.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-five.md new file mode 100644 index 000000000..ca9d98430 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-five.md @@ -0,0 +1,172 @@ +# Chapter 5 - The Session and the Session Manager + +### 5.1 Open RV Session + + +Each viewer window represents an RV session. A session is composed of one or more source movies, frame markers, image transforms, color corrections, and interactive states (like caching and playback speed). The source movies are combined according to the session type. The default RV session type is \`\`sequence,'' which plays back the source movies one after another. + +An RV session can be saved as an .rv file. The .rv file contains the entire state of its image processing tree—all of the variables that determine how it will work—as well as information about frame ranges, in/out points, etc. The .rv file stores references to movie files and images; it does not make copies of them. If you change source material on disk and load an .rv file that references those materials, the changes will be evident in RV. + +The .rv file is a GTO file. Tools that operate on GTO files can be used on .rv files. C++ and Python source code is available for creating, reading, and manipulating GTO files, including the ASCII GTO files used by RV. [See RV File Format](../rv-reference-manual/rv-reference-manual-chapter-six.md). + +### 5.1.1 What's in a Session + +A session is represented internally as a Directed Acyclic Graph (DAG) in which images and audio pass from the leaves to the root where they are rendered. Each node in the DAG can have a number of parameters or state variables which control its behavior. RV's user interface is essentially a controller which simply changes these parameters and state variables. An .rv file contains all of the state variables for the nodes in RV's image processing DAG. A description of each of the node types can be found in the Reference Manual. + +You can use the gtoinfo program to view the contents of an .rv file from the command line. Other GTO tools like the python module can be used to edit .rv files without using RV itself. + +The DAG nodes that are visible in the user interface are called Views. RV provides three default views, and the ability to make views of your own. In addition to any Sources you've loaded, the three views that all sessions have are the Default Sequence, which shows you all your sources in order, the Default Stack, which shows you all your sources stacked on top of one another, and the Default Layout, which has all the sources arranged in a grid (or a column, row, or any other custom layout of your own design). In addition to the default views, you can create any number of Sources, Sequences, Stacks, and Layouts of your own. Whenever a Source is added to the session, it is automatically added to the inputs of each of the default views, not to user-defined views. + +### 5.2 Session Manager + + +![30_session_manager.png](../../images/rv-user-manual-30-rv-cx-session-manager-29.png) ![31_session_manager2.png](../../images/rv-user-manual-31-rv-cx-session-manager2-30.png) + +Figure 5.1: Session Manager on the Mac showing Inputs and Sequence Edit Panel. The DefaultSequence is being viewed. + +The session manager is used to examine and edit the contents of an RV session. The session manager shows an outline of the session contents from which you can create, modify, and edit new sequences, stacks, layouts, and more. By default, the Session Manage comes up \`\`docked'' at the left side of the RV window, but it can be un-docked (by clicking and dragging on the title) and positioned as a separate window, or docked at another edge of the RV window. + +The session manager interface is in two parts: the top panel shows an outline of the session contents, and the bottom shows either the inputs of the currently viewed object or user interface to edit the current view. By double clicking on an icon in the top portion of the session manager you can switch to another view. By default RV will create a default sequence, stack, and layout which includes all of the sources in the session. When a new source is added, these will be automatically updated to include the new source. + +### 5.3 Creating, Adding to, and Removing from a View + + +![32_ease_sm_add_menu.png](../../images/rv-user-manual-32-rv-cx-ease-sm-add-menu-31.png) ![33_e_sm_folder_menu.png](../../images/rv-user-manual-33-rv-cx-e-sm-folder-menu-32.png) + +Figure 5.2: + +The Add View and Folders Menus + +A new view can be created via the add view menu. The menu is reachable by either the add view (+) button or by right clicking with the mouse in the session outline. Anything selected in the session outline becomes a member (input) of the newly created view. Alternately you can create a view and then add or subtract from it afterwards. + +The top items in the view create new views from existing views. The bottom items create new sources which can be used in other views. + +Folder views can be created either from the add menu or the folders menu. The folders menu lets you create a folder from existing views or with copies of existing views. When a view is copied in the session manager, the copy is really just a reference to a single object. + +You can add to an existing view by first selecting it by double clicking on it, then dragging and dropping items from the session outline into the inputs section of the session manager. + +Drag and drop of input items makes it possible to rearrange the ordering of a given view. For example, in a sequence the items are played in the order the appear in the inputs list. By rearranging the items using drag and drop or the up and down arrows in the inputs list you can reorder the sequence. + +To remove an item from a view select the item(s) in the inputs list and hit the delete (trash can) button to the right of the inputs list. Similarly, the trashcan button in the upper panel well delete a view from the session. **Please Note** : neither of these remove/delete operations is undoable. + +### 5.4 Navigating Between Views + + +For each RV session, there is always a single \`\`current view'', whose name is displayed at the top of the Session Manager. As in a web browser, RV remembers the history of views you have \`\`visited'' and you can go backwards and forwards in that history. + +To change to a different View you can: + +* Double-click on any of the views listed in the top panel of the Session Manager +* Double-click on any input view of the current view (listed in the Inputs tab of the bottom panel of the Session Manager) +* Double-click on any visible image in the main RV window +* Click the left (backwards) and right (forwards) buttons at the top of the Session Manager +* For backwards compatibility, the items at the top of the Tools menu navigate to the usual default views + +Once you have changed views, you can go backwards and forwards in the view history with the arrow buttons at the top of the Session Manager, or with the navigation hot keys \`\`Shift-left-arrow'' (backwards) and \`\`Shift-right-arrow'' (forwards). Note that you can easily navigate between views with out the Session Manager by double-clicking on the image to \`\`drill-down'' and then using \`\`Shift-left-arrow'' to go back. + +### 5.5 Source Views + + +Source Views are the \`\`leaves'' of the graph in that they are views with no inputs (since they get their pixels from some external source, usually files on disk somewhere). The Edit interface for source views is currently used only to adjust editorial information (in the future it may provide access to other per-source information like color corrections, LUTs, etc). In RV, each source has an Cut In/Out information which provide editorial information to views that use that source (like a Sequence view). These In/Out frame numbers can be set from the command line, or changed with the Edit panel of the Source View interface. + +![34_ase_sourceSMShot.png](../../images/rv-user-manual-34-rv-cx-ase-sourceSMShot-33.png) + +Figure 5.3: + +The Source Edit Interface + +By default, \`\`Sync GUI In/Out to Source'' is checked, and you can manipulate the Cut In/Out numbers by setting the in/out frames in the timeline in all the usual ways (see Section [4.5.4](rv-user-manual-chapter-four.md#454-in-and-out-points)). You can also type frame numbers in the given fields, use the up/down nudge keys, or the mouse wheel (after clicking in the relevant field). + +### 5.5.1 Source Media Containing Multiple Images (Subcomponents) + +In the session manager, a source can be opened revealing the media it is composed of. If the media has multiple layers and/or views (e.g. a stereo OpenEXR file) the media can be further opened to reveal these. + +When multiple layers or views (subcomponents) are present in media the session manager will present a radio button interface in one of its columns. Each subcomponent in the media has its own selectable toggle button. When a subcomponent is selected, the source will show only that subcomponent; stereo or any other multiple view effect will be turned off. + +You can go back to the default by either double clicking on the media or deselecting the selected subcomponent (toggle it off). + +In addition to restricting the media to one of its subcomponents, the session manager also allows you to build new views which include more than one subcomponent. For example if you select all the layers in a multiple layer OpenEXR file, you can create a layout view (right popup menu or the '+' tool button) that shows all of them simultaneously. When RV does this, it creates new temporary sources dedicated to the subcomponent views, layers, or channels that were selected. These subcomponent sources are placed in their own folder. + +It's also possible to drag and drop subcomponents into existing view inputs. + +### 5.6 Group Views + + +Folders, Sequence Views, Stack Views, Switch Views, and Layout Views are all \`\`Group Views'' in that they take multiple inputs and combine them in some way for viewing. A Sequence plays it's inputs in order, a Stack layers it's aligned inputs on top of each other, and a Layout arranges it's inputs in a grid, row, column or arbitrary user-determined format. + +Some interface is shared by all Group Views: + +The Group interface gives you control over the resolution of it's output. During interactive use, RV's resolution invariance means that the aspect ratio is the only important part of the size, but during output with RVIO, this size would be the default output resolution. If Size Determined from Inputs' is checked, the group take it's size from the maximum in each dimension of all it's inputs. If the size is not being programmatically determined, you can specify any size output in the provided fields. + +Similarly, the output frame rate can be specified in the Output FPS field. This is the frame rate that is used as the default for any RVIO output of this group, and is also passed to any view for which this group is an input. The output FPS is initialized from the default frame rate of the first input added to the group. If Retime Inputs to Output FPS is checked, inputs whose native frame rate differs from the group's output fps will be retimed so that they play correctly at the output fps. That is, a real-time pull up/down will be performed on the video, and the audio will be resampled to play at the output fps while preserving pitch. + +As long as Use Source Cut Information is checked in the Group interface, the group will adopt the editorial cut in/out information provided by the sources (see Section [5.5](#55-source-views) ). This is particularly useful in the case of sequences, but also comes up with stacks and layouts, when, for example, you want to compare a matching region of movies with different overall frame ranges. + +![37_e_sequenceSMShot.jpg](../../images/rv-user-manual-37-rv-cx-e-sequenceSMShot-36.jpg) ![38_ease_stackSMShot.jpg](../../images/rv-user-manual-38-rv-cx-ease-stackSMShot-37.jpg) ![39_ase_layoutSMShot.jpg](../../images/rv-user-manual-39-rv-cx-ase-layoutSMShot-38.jpg) + +Table 5.1: + +Group View Interfaces: Sequence, Stack, and Layout + +#### 5.6.1 Sequence Views + +A Sequence view plays back its inputs in the order specified in the Inputs tab of the the Squence interface. The order can be changed by dragging and dropping in the Inputs, or by selecting and using the arrow keys to the right of the list of Inputs. An input can be removed (dropped from the sequence) by selecting the input and then clicking the trashcan button. + +In addition to the order of the clips being determined by the order of the inputs, the actual cut in/out points for each clip can also be specified. At the moment, the easiest way to do this is to specify cut information for each source that you want to appear in the sequence with the Source view interface described in Section [5.5](#55-source-views) . As long as Use Source Cut Information is checked in the Sequence interface, the sequence will adopt the editorial cut in/out information provided by the sources. + +Since the sequence only shows you one input at a time, if Fill View with Content is checked and the sequence is the current view, the output size of the sequence will be dynamically adjusted so that the \`\`framed'' content always fills the RV window, even if different inputs of the sequence have different aspect ratios. + +#### 5.6.2 Stack Views + +The Stack View presents it's inputs \`\`on top'' of each other, for comparing or compositing. In this case the order of the inputs determines the stacking order (first input on top). In addition the usual ways of reordering the inputs, you can \`\`cycle'' the stack forwards or backwards with items on the Stack menu, or with hotkeys: ')' and '('. + +The compositing operation used to combine the inputs of the stack can be selected in the Edit interface. At the moment, you can choose from Over, Replace, Add, Difference, and Inverted Difference. + +Because any or all of the inputs to the Stack may have audio, you can select which you want to hear. Either mix all the audio together (the default), play only the audio from the topmost input in the stack, or pick a particular input by name. + +By default, stack inputs will be displayed so that matching \`\`source'' frame numbers are aligned. For example if you stack foo.121-150#.exr on top of goo.56-200#.exr, you'll see frame foo.121.exr on top of goo.121.exr even though the two sequences have completely different frame ranges. If you don't want this behavior and you want the start frames of the inputs to be aligned regardless of their frame numbers, check Align Start Frames. + +Also note that the Wipes mode is useful when comparing images in a stack. The use of wipes is explained in Section [4.4.3](rv-user-manual-chapter-four.md#443-comparing-images-with-wipes) . + +#### 5.6.3 Layout Views + +A Layout is just what it sounds like; the inputs are arranged in a grid, column, row, or arbitrary user-defined pattern. In many ways, a Layout is similar to a stack (it even has a compositing operation for cases where you arrange on input \`\`over'' another). All the interface actions described in Section [5.6.2](#562-stack-views) for Stack Views also apply to Layout Views. + +To determine the arrangement of your layout, choose one of five modes. There are three procedural modes, which will rearrange themselves whenever the inputs are changed or reordered: Packed produces a tightly packed or tiled pattern, Row arranges all the inputs in a horizontal row, and Column arranges the inputs in a vertical column. If you want to position your inputs by hand, select the Manual mode. In this mode hovering over a given input image will show you a manipulator that can be used to reposition the image (by clicking and dragging near the center) or scale the image (by clicking and dragging the corners). After you have the inputs arranged to your liking, you may want to switch to the Static mode, which will no longer draw the manipulators, and will leave the images in your designated arrangement. + +#### 5.6.4 Switch Views + +A Switch is a conceptually simpler than the other group views: it merely switches between its inputs. Only one input is active at a time and both the imagery and audio pass through the switch view. Otherwise, the switch shares the same output characteristics as the other group nodes (resolution, etc). + +### 5.7 Retime View + + +The Retime View takes a single input and alters it's timing, making it faster or slower or offsetting the native frame numbers. For example, to double the length of an input (IE make every frame play twice, which will have the effect of slowing the action without changing the frame rate), set the Length Multiplier to 2. Or to have frame 1 of the input present itself on the output as frame 101, set the Offset to 100. + +The Length Multiplier and Offset apply to both the video and audio of the input. If you want to apply an additional scale or offset to just the audio, you can use the Audio Offset and Audio Scale fields. + +![40_ase_retimeSMShot.jpg](../../images/rv-user-manual-40-rv-cx-ase-retimeSMShot-39.jpg) + +Figure 5.6: Retime View Edit Interface + +### 5.8 Folders + +Folders are special kind of group view used to manage the contents of the session manager. Unlike other views in RV, when you create a folder its inputs will appear as a hierarchy in the session manager. You can drag and drop and move and copy views in and out of folders to organize them. They can be used as an input just like any other view so they can be nested, placed in a sequence, stack, or layout and can be manipulated in the inputs interface in the same way other views are. + +Folders have no display behavior themselves, but they can display their contents as either a switch or a layout. + +You can change how a folder is displayed by selecting either Layout or Switch from its option menu. + +When a view becomes a member of a folder, it will no longer appear in one of the other categories of the session manager. If a view is removed as a member of a folder, it will once again appear in one of the other categories. + +![41_cxx98-release_folders.jpg](../../images/rv-user-manual-41-rv-cxx98-release-folders-40.jpg) + +Figure 5.7: Folders in the Session Manager + +#### 5.8.1 Folders and Drag and Drop + +You can drag one or more views into a folder in the session manager to make it a member (input) of the folder. To make a copy of the dragged items hold down the drag copy modified while dragging. On the mac this is the option key, on Windows and Linux use the control key. + +The session manager will not allow duplication of folder members (multiple copies of the same view in a folder) although this is not strictly illegal in RV. + +Drag and drop can also be used to reorder the folder contents the same way the inputs are reordered. An insertion point will be shown indicating where the item will move to. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-four.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-four.md new file mode 100644 index 000000000..15af8155d --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-four.md @@ -0,0 +1,644 @@ +# Chapter 4 - User Interface + +The goal of RV's user interface is to be minimal in appearance, but complete in function. By default, RV starts with no visible interface other than a menu bar and the timeline, and even these can be turned off from the command line or preferences. While its appearance is minimal, its interaction is not: almost every key on the keyboard does something and it's possible to use key-chords and prefix-keys to extend this further. + +Emacs users will find this feature familiar. RV can have prefix-keys that when pressed remap the entire keyboard or mouse bindings or both. + +The main menu and pop-up menus allow access to most functions and provide hot keys where available. + +RV makes one window per session. Each window has two main components: the viewing area—where images and movies are shown— and the menu bar. On macOS, the menu bar will appear at the top of the screen (like most native macOS applications). On Linux and Windows, each RV window has its own attached menu bar. + +A single RV process can control multiple independent sessions on all platforms. On the Mac and Windows, it is common that there be only a single instance of RV running. On Linux it is common to have multiple separate RV processes running. + +Many of the tools that RV provides are heads up widgets. The widgets live in the image display are or are connected to the image itself. Aside from uniformity across platforms, the reason we have opted for this style of interface was primarily to make RV function well when in full screen mode. + +![4_linux_snapshot.jpg](../../images/rv-user-manual-4-rv-cxx-rv-linux-snapshot-03.jpg) ![5_osx_snapshot.jpg](../../images/rv-user-manual-5-rv-cxx-rv-osx-snapshot-04.jpg) + +Table 4.1: RV on Linux and on macOS. + +### 4.1 Feedback + + +RV provides feedback about its current state near the top left corner of the window. + +![6_rv_rv-cxx98-release_feedback.jpg](../../images/rv-user-manual-6-rv-cxx98-release-feedback-05.jpg) + +Figure 4.1: Feedback widget indicating full-color display + +### 4.2 Main Window Tool Bars + + +The main RV window has two toolbars which are visible by default. The upper toolbar controls which view is displayed, viewing options, and current display device settings. The lower toolbar control play back, has tool buttons to show more functions, and audio controls. + +![7_toolbars_legend.png](../../images/rv-user-manual-7-rv-cxx-toolbars-legend-06.png) + +Figure 4.2: + +Tool Bar Controls + +The lower toolbar is in three sections from left to right: tool launch buttons, play controls, and audio/loop mode. The tool launch buttons toggle rv's main user interface components like the session manager or the heads-up timeline. The play controls control play back in the current view. These are similar to the heads-up play controls available from the timeline configuration. The loop mode determines what happens at the end of the timeline and the audio controls modify the volume and mute. + +The frame content, display device settings, channel view, and stereo mode on the top tool bar are also available under the View menu. See Chapter [7](rv-user-manual-chapter-seven.md#7-how-a-pixel-gets-from-a-file-to-the-screen) for more information on what these settings do. + +The full screen toggle is also under the Window menu. + +You can toggle the visibility for each of the tool bars under the Tools menu. + +### 4.3 Loading Images, Sequences, Movies and Audio + + +#### 4.3.1 Using the File Browser + +There are two options for loading images, sequences, and movies via the file browser: you can add to the existing session by choosing File → Open (or File → Open into Layer) or you can open images in a new window by choosing File → Open In New Session. + +In the file browser, you may choose multiple files. RV tries to detect image sequences from the names of image files (movie files are treated as individual sequences). If RV detects a pattern, it will create an image sequence for each unique pattern. If no pattern is found, each individual image will be its own sequence. + +Audio files can be loaded into RV using the file browser. To associate an audio file with its corresponding image sequence or movie open the audio file as a layer using File → Open into Layer. The first sequence in the layer will determine the overall length of the source, e.g. a longer audio file loaded as a layer after a sequence will be truncated to the duration of the sequence. Any number of Audio files can be added as layers to the same source and they will be mixed together on playback. + +The file browser has three file display modes: column view, file details view, and media details view. Sequences of images appear as virtual directories in the file browser: you can select the entire sequence or individual files if you open the sequence up. **Note:** You can multi-select in File Details and Media Details, but not in Column View. In general the File Details view will be the fastest. + +![8_rv_rv-cxx98-release_grabFile.png](../../images/rv-user-manual-8-rv-cxx98-release-grabFile-07.png) + +Figure 4.3: File Browser Show File Details + +Favorite locations can be remembered by dragging directories from the main part of the file browser to the side bar on the left side of the dialog box. Recent items and places can be found under the path combo box. You can configure the way the browser uses icons from the preferences drop down menu on the upper right of the browser window. + +#### 4.3.2 Dragging and Dropping + +On all platforms, you can drag and drop file and folder icons into the RV window. RV will correctly interpret sequences that are dropped (either as multiple files or inside of a directory folder that is dropped). LUT and CDL files can also be dropped. + +RV uses smart drop targets to give you control over how files are loaded into RV. You can drop files as a *source* or as a *layer* . As you drag the icons over the RV window, the drop targets will appear. Just drop onto the appropriate one. + +On Linux RV should be compatible with the KDE and Gnome desktops. It is possible with these desktops (or any that supports the XDnD protocol) to drag file icons onto an RV window. + +If multiple icons are dropped onto RV at the same time, the order in which the sequences are loaded is undefined. + +To associate an audio file with an image sequence or movie, drop the audio file as a layer, rather than as a source. + +### 4.4 Examining an Image + + +RV normalizes image geometry to fit into its viewing window. If you load two files containing the same image but at different resolutions, RV will show you the images with the same apparent \`\`size''. So, for example, if these images are viewed as a sequence — one after another — the smaller of the two images will be scaled to fit the larger. Of course, if you zoom in on a high-resolution image, you will see detail compared to a lower-resolution image. When necessary you can view the image scaled so that one image pixel is mapped to each display pixel. + +On startup, RV will attempt to size the window to map each pixel to a display pixel, but if that is not possible, it will settle on a smaller size that fits. You can always set the scale to 1:1 with the '1' hotkey, and if it is possible to resize the window to contain the entire image at 1:1 scaling, you can do so via the Window → Fit or Window → Center Fit menu items. + +#### 4.4.1 Panning, Zooming, and Rotating + +You can manipulate the Pan and Zoom of the image using the mouse or the row of number keys (or the keypad on an extended keyboard if numlock is on). By holding down the control key (apple key on a mac) and the left mouse button you can zoom the image in or out by moving to the left and right. By holding down the alt key (or option key on a mac) you can pan the image in any direction. If you are accustomed to Maya camera bindings, you can use those as well. + +Rotating the image is accessible from the Image → Rotation menu. By selecting Image → Rotation → Arbitrary it's possible to use the mouse to scrub the rotation as a parameter. + +To frame the image — automatically pan and zoom it to fit the current window dimensions — hit the 'f' key. If the image has a rotation, it will remain rotated. + +To precisely scale the image you can use the menu Image → Scale to apply one of the preset scalings. Selected 1:1 will draw one image pixel at every display pixel. 2:1 will draw 1 image pixel as 4 display pixels, etc. + +#### 4.4.2 Inspecting Pixel Values + +The pixel inspector widget can be accessed from the Toools menu or by holding down Shift and clicking the left mouse button. The inspector will appear showing you the source pixel value at that point on the image (see Figure [cap:Color-Inspector](#color-inspector) ). If you drag the mouse around over the image while holding down the shift key the inspector widget will also show you an average value (see Figure [cap:Average-Color](#average-color) ). You can move the widget by clicking on it and dragging. + +To remove inspector widget from the view either movie the mouse to the top left corner of the widget and click on the close button that appears or toggle the display with the Tools menu item or hot key. + +The inspector widget is locked to the image. If you pan, zoom, flip, flop, or rotate the image, the inspector will continue to point to the last pixel read. + +If you play a sequence of images with the inspector active, it will average the pixel values over time. If you drag the inspector while playback occurs it will average over time and space. + +RV shows either the source pixel values or the final rendered values. The source value represents the value of the pixel just after it is read from the file. No transforms have been done on the pixel value at that point. You can see the final pixel color (the value after rendering) by changing the pixel view to the final pixel value from the right-click popup menu. + +The value is normalized if the image is stored as non-floating point — so values in these types of images will be restricted to the [0,1] range. Floating point images pass the value through unchanged so pixels can take values below zero or above one. Table [7.3](rv-user-manual-chapter-seven.md#characteristics-of-channel-data-types) shows the range of each of the channel data types. + +![11_rv_rv-cxx98-release_inspector.jpg](../../images/rv-user-manual-11-rv-cxx98-release-inspector-010.jpg) + +Figure 4.6: Color inspector + +![12_rv_rv-cxx98-release_average.jpg](../../images/rv-user-manual-12-rv-cxx98-release-average-11.jpg) + +Figure 4.7: Average Color + +From the right-click popup menu it's possible to view the pixel values as normalized to the [0,1] range or as 8, 16, 10, or 12 bit integer values. + +#### 4.4.3 Comparing Images with Wipes + +Wipes allow you to compare two or more images or sequences when viewing a stack. Load the images or sequences that you wish to compare into RV as sources (not as layers). Put RV into stack mode or create a stack view from the Session Manager, and enable wipes from the Tools menu or with the “F6” hot key. Now you can grab on the edges of the top image and wipe them back to reveal the image below. You can grab any edge or corner, and you can move the entire wipe around by grabbing it in the exact center. Also, by clicking on the icon that appears at the center or corner of a wipe or via the Wipes menu, you can enable the wipe information mode, that will indicate which edge you are about to grab. + +Wipes can be used with any number of sources. The stack order can be cycled using the “(“ and “)” keys. + +#### 4.4.4 Parameter Edit Mode and Virtual Sliders + +RV has a special UI mode for editing the parameters such as color corrections, volume, and image rotation. For example, hitting the \`\`y'' key enters gamma edit mode. When editing parameters, the mouse and keyboard are bound to a different set of functions. On exiting the editing mode, the mouse and keyboard revert to the usual bindings. (See Table [4.2](#parameter-edit-mode-key-and-mouse-bindings) ) + +To edit the parameter value using the mouse you can either scrub (like a virtual slider) or use the wheel. If you want to eyeball it, hold the left mouse down and scrub left and right. By default, when you release the button, the edit mode will be finished, so if you want to make further changes you need to re-enter the edit mode. If you want to make many changes to the same parameter, you can “lock” the mode with the 'l' key. The scroll wheel increments and decrements the parameter value by a predefined amount. Unlike scrubbing with the left mouse button, the scroll wheel will not exit the edit mode. When multiple Sources are visible, as in a Layout view, parameter sliders will affect all Sources. Or you can use 's' to select only the source under the pointer for editing. You can exit the edit mode by hitting the escape key or space bar (or most other keys). + +To change the parameter value using the keyboard, hit the Enter (or Return) key; RV will prompt you for the value. For interactive changes from the keyboard, use the \`\`+'' and \`\`-'' keys (with or without shift held down). The parameter is incremented and decremented. To end the keyboard interactive edit, hit the Escape or Spacebar keys. + +| Key/Mouse Sequence | Action | +| ---------------------- | -------------------------------------- | +| Mouse Button #1 Drag | Scrub parameter | +| Mouse Button #1 Up | Finish parameter edit | +| Wheel | Increment or decrement parameter | +| Enter | Enter parameter numerically | +| 0 through 9 | Enter parameter numerically | +| ESC | Cancel parameter edit mode | +| \+ or = | Increment parameter value | +| - or _ | Decrement parameter value | +| BACKSPACE or DEL | Reset parameter value to default | +| r or g or b | Edit single channel of color parameter | +| c | Edit all channels of color parameter | +| l | Lock or unlock editing mode | +| s | Select single Source for editing | + +Table 4.2: Parameter Edit Mode Key and Mouse Bindings + +#### 4.4.5 Image Filtering + +When image pixels are scaled to be larger or smaller than display pixels, resampling occurs. When the image is scaled (zoomed) RV provides two resampling methods (filters): nearest neighbor and linear interpolation (the default). + +You can see the effects of the resampling filters by making the scale greater than 1:1. This can be done with any of the hot keys \`\`2'' through \`\`8'' or by zooming the image interactively. When the image pixels are large enough, you can switch the sampling method via View → Linear Filter or by hitting the \`\`n'' key. Figure [4.3](#nearest-neighbor-and-linear-interpolation-filtering) shows an example of an image displayed with nearest neighbor and linear filtering. + +![14_e_nearest_filter.jpg](../../images/rv-user-manual-14-rv-cx-e-nearest-filter-13.jpg) ![15_se_linear_filter.jpg](../../images/rv-user-manual-15-rv-cx-se-linear-filter-14.jpg) + +Table 4.3: Nearest Neighbor and Linear Interpolation Filtering. Nearest neighbor filtering makes pixels into blocks (helpful in trying to determine an exact pixel value). + +##### Digression on Resampling + +It's important to know about image filtering because of the way in which RV uses the graphics hardware. When an image is resampled—as it is when zoomed in—and the resampling method produces interpolated pixel values, correct results are really only obtained if the image is in linear space. Because of the way in which the graphics card operates, image resizing occurs before operations on color. This sequence can lead to odd results with non-linear space images if the linear filter is used (e.g., cineon files). + +If you want to put a positive spin on it you could say you're using a non-linear resampling method on purpose. The results are only incorrect if you meant to do something else! + +There are two solutions to the problem: use the nearest neighbor filter or convert the image to linear space before it goes to the graphics card. The only downside with the second method is that the transform must happen in software which is usually not as fast. Of course this only applies to images that are not already in linear space. + +Why does RV default to the linear filter? Most of the time, images and movies come from file formats that store pixel values in linear (scene-referred) space so this default is not an issue. It also looks better. + +The important thing is to be aware of the issue. + +##### Floating Point Images + +If RV is displaying floating point data directly, linear filtering may not occur even though it is enabled. This is a limitation of some graphics cards that will probably be remedied (via driver update or new hardware) in the near future. In this case You can make the filter work by disallowing floating point values via Image → Color Resolution → Allow Floating Point. Many graphics can do filtering on 16 bit floating point images but cannot do filtering on 32 bit floating point images. RV automatically detects the cards capabilities and will turn off filtering for images if necessary. + +Figure [4.4](#floating-point-filter) shows an example of a floating point image with linear filtering enabled versus equivalent 8-bit images. + +![16_ase_float_linear.jpg](../../images/rv-user-manual-16-rv-cx-ase-float-linear-15.jpg) ![17_ease_8bit_linear.jpg](../../images/rv-user-manual-17-rv-cx-ease-8bit-linear-16.jpg) ![18_ase_8bit_nearest.jpg](../../images/rv-user-manual-18-rv-cx-ase-8bit-nearest-17.jpg) + +Table 4.4: Floating point linear, 8 bit linear, and 8 bit nearest neighbor filtering. + +Graphics hardware does not always correctly apply linear filtering to floating point images. Filtering can dramatically change the appearance of certain types of images. In this case, the image is composed of dense lines and is zoomed out (scaled down). + +#### 4.4.6 Big Images + +RV can display any size image as long as it can fit into your computer's memory. When an image is larger than the graphics card can handle, RV will tile the image display. This makes it possible to send all the pixels of the image to the card for display. The downside is that all of the pixels are sent to the display even though you probably can't see them all. However, if you zoom in (for example hit \`\`1'' for 1:1 scale) when a large image is loaded, RV will only draw pixels that are visible. + +One of the constraints that determines how big an image can be before RV will tile it is the amount of available memory in your graphics card and limitations of the graphics card driver. On most systems, up to 2k by 2k images can be displayed without tiling (as long as the image has 8-bit integer channels). In some cases (newer cards) the limit is 4k by 4k. However, there are other factors that may reduce the limit. + +If your window system uses the graphics card (like macOS or Linux with the X.org X server) or there are other graphics-intensive applications running, the amount of available memory may be dependent on these processes as well. Alternately, because RV wants to use as much graphics memory as it can, RV may cause graphics resource depletion that affects other running applications that should have higher priority. Because of this, RV has the capability to limit its graphics memory usage. You can specify this in RV's Preferences by editing the *Maximum VRAM Usage Per Tile* or on the command line with the -vram option. + +Over time, these problems will go away as drivers and operating systems become smarter about graphics resource allocation. + +If you reduce the VRAM usage, RV will tile images of smaller size. For sequences, this may affect playback speed since tiling is slightly less efficient than not tiling. Tiling also affects interactive speed on single images; if tiling is not on, RV can keep all of the image pixels on the graphics card. If tiling is on, RV has to send the pixels every time it redraws the image. + +You can determine if RV is tiling the image by looking the image info widget under Tools → Image Info. If tiling is on there will be an entry called \`\`DisplayTiling'' showing the number of tiles in X and Y. + +#### 4.4.7 Image Information + +The image information widget, can be shown or hidden via the Tools → Image Info menu item or using the hot key: \`\`i''. You can move the widget by clicking and dragging. The widget shows the geometry and data type of the image as well as associated meta-data (attributes in the file). Figure [4.9](#image-information-widget) shows an example of the information widget. + +![19_e_infoWidgetShot.png](../../images/rv-user-manual-19-rv-cx-e-infoWidgetShot-18.png) + +Figure 4.9: Image Information Widget. + +Channel map information—the current mapping of file channels to display channels—is displayed by the info widget as well as the names of channels available in the image file; this display is especially useful when viewing an image with non-RGBA channels. + +If the image is part of a sequence or movie the widget will show any relevant data about both the current image as well as the sequence it is a part of. For movie files, the codecs used to compress the movie are also displayed. If the movie file has associated audio data, information about that will also appear. + +To remove the image information widget from the view either move the mouse to the top left corner of the widget and click on the close button that appears or toggle the display with the Tools menu item or hot key ('i'). + +### 4.5 Playing Image Sequences, Movie Files, and Audio Files + + +RV can play multiple images, image sequences and movie files as well as associated audio files. Play controls are available via the menus, keyboard, and mouse. Timing information and navigation is provided by the timeline widget which can be toggled via the Tools → Timeline menu item or by hitting the TAB key. + +#### 4.5.1 Timeline + +![20_timeline_labelled.png](../../images/rv-user-manual-20-rv-cx-timeline-labelled-19.png) + +Figure 4.10: Timeline With Labelled Parts + +This timeline shows in and out points, frame count between in and out points, total frames, target fps and current fps. In addition, if there are frame marks, these will appear on the timeline as seen in Figure [4.5.5](#timline-with-marks) . + +The current frame appears as a number positioned relative to the start frame of the session. If in and out points are set, the relative frame number will appear at the left side of the timeline — the total number of frames between the in and out points is displayed below the relative frame number. + +Tip: To change the start frame of a Version (with or without Slate): Toggle **Movie Has Slate** (or **Frames have Slate**) to on, and then set the First Frame and Last Frame Fields on the Version. If your Movie (or Frames) are 201 to 299 and 201 is the slate, you need to set First Frame to 202. + +By clicking anywhere on the timeline, the frame will change. Clicking and dragging will scrub frames, as will rolling the mouse-wheel. Also note that you can shift-click drag to set an in/out range. + +The in/out range can also be manipulated with the mouse. You can grab and drag either end, or grab in the middle to drag the whole range. + +There are two FPS indicators on the timeline. The first indicates the target FPS, the second the actual measured playback FPS. + +| | | +| -------------------------- | ---------------------------------------------------------------------------------- | +| [ | Set in point | +| ] | Set out point | +| \ | Clear in/out points | +| right-arrow | Step one frame to the right. | +| left-arrow | Step on frame to the left. | +| alt-right-arrow | Move current frame to next mark (or source boundary, if there are no marks) | +| alt-left-arrow | Move current frame to previous mark (or source boundary, if there are no marks) | +| ctrl(mac: cmd)-left-arrow | Set in/out to next pair of marks (or source boundaries, if there are no marks) | +| ctrl(mac: cmd)-right-arrow | Set in/out to previous pair of marks (or source boundaries, if there are no marks) | +| down-arrow, spacebar | Toggle playback | +| up-arrow | Reverse play direction | + +Table 4.5: Useful Timeline Hotkeys + +![21_timeline_reddot.png](../../images/rv-user-manual-21-rv-cx-timeline-reddot-20.png) + +**Note:** A red dot with a number indicates how many frames RV has lost since the last screen refresh. + +#### 4.5.2 Timeline Configuration + +The timeline can be configured from its popup menu. Use the right mouse button anywhere on the timeline to show the menu. If you show the popup menu by pointing directly at any part of the timeline, the popup menu will show that frame number, the source media there, and the operations will all be relative to that frame. For example, without changing frames you can set the in and out point or set a mark via the menu. + +By default the timeline will show the \`\`source” frame number, the native number of the media. Alternately you can show the global frame number, global time code, or even the \`\`Footage” common in traditional animation (16 frames per foot). + +![22_timelineMenuShot.jpg](../../images/rv-user-manual-22-rv-cx-timelineMenuShot-21.jpg) + +Figure 4.12: Timeline Configuration Popup Menu + +The Configuration menu has a number of options: + +| | | +| --- | --- | +| Show Play Controls | Hide or Show the playback control buttons on the right side of the timeline | +| Draw Timeline Over Imagery | This was the default behavior in previous versions of RV. The timeline is now drawn in the margin by default | +| Position Timeline At Top | Draw the timeline at the top of the view. The default is to draw it at the bottom of the view. | +| Show In/Out Frame Numbers | When selected, the in and out points will be labeled using the current method for display the frame (global, source, or time code). | +| Step Wraps At In/Out | This controls how the arrow keys behave at the in and out point. When selected, the frame will wrap from in to out or vice versa. | +| Show Source/Input at Frame | When selected, the main media file name for the frame under the pointer (not the current frame) will be shown just above or below the timeline. | +| Show Play Direction Indicator | When selected, a small triangle next to the current frame indicates the direction playback will occur, when started. | + +#### 4.5.3 Realtime versus Play All Frames + +Control → Play All Frames determines whether RV should skip frames or not if it is unable to render fast enough during playback. Realtime mode (when play all frames is not selected) uses a realtime clock to determine which frame should be played. When in realtime mode, audio never skips, but the video can. When play all frames is active, RV will never skip frames, but will adjust the audio if the target fps cannot be reached. + +When the timeline is visible, skipped frames will be indicated by a small red circle towards the right hand side of the display. The number in the circle is the number of frames skipped. + +#### 4.5.4 In and Out Points + +There are two frame ranges associated with each view in an RV Session: the start and end frames and the in and out frames (also known as in and out points). The in and out points are always within the range of the start and end frames. RV sets the start and end frames automatically based on the contents of the view. The in and out points are set to the start and end frames by default. However, you can set these points using the \`\`[“ and \`\`]'' keys, or by right-clicking on the timeline. You can also set an in/out range by shift-dragging with the left-button in the Timeline or the Timeline Magnifier. The in/out range displayed in the timeline can also be changed with the mouse, either by dragging the whole range (click down in the middle of the range), or by dragging one of the endpoints (click down on the endpoint).To reset the in and out points, use the \`\`\\'' key. + +If frames have been marked, RV can automatically set the in and out points for you based on them (use the ctrl-right/left-arrow keys, or command-right/left-arrow on Mac). + +#### 4.5.5 Marks + +A mark in RV is nothing more than a frame number which can be stored in an RV file for later use. To toggle a frame as being marked, use Mark → Mark Frame (or use the \`\`m'' hotkey). The timeline will show marks if any are present. + +While not very exciting in and of themselves, marks can be used to build more complex actions in RV. For example, RV has functions to set the in and out points based on marks. By marking shot boundaries in a movie file, you can quickly loop individual shots without selecting the in and out points for each shot. By selecting Mark → Next Range From Marks and Mark → Previous Range From Marks or using the associated hot keys \`\`control+right arrow” or \`\`control+left arrow” the in and out points will shift from one mark region to the next. + +Marking and associated hot keys for navigating marked regions quickly becomes indispensable for many users. These features make it very easy to navigate around a movie or sequence and loop over part of the timeline. Producers and coordinators who often work with movie files of complete sequences (for bidding or for client reviews) find it useful to mark up movie at the shot boundaries to make it easy to step through and review each shot. + +![23_timelineMarksShot.png](../../images/rv-user-manual-23-rv-cx-timelineMarksShot-22.png) + +Figure 4.13: Timline with Marks + +#### 4.5.6 Timeline Magnifier + +The Timeline Magnifier tool (available from the **Tools** menu, default hotkey F3) brings up a special timeline that is \`\`zoomed'' to the region bounded by the In/Out Points. In addition to showing only the in/out region, the timeline magnifier differs from the standard timeline in that it shows frame ticks and numbers on every frame, if possible. If there is not enough room for frames/ticks on every frame, the magnifier will fall back to frames/ticks every 5 or 10 frames. The frame numbers of the in/out points are displayed at either end of the magnified timeline. + +##### Audio Waveform Display + +The timeline magnifier can display the audio waveform of any loaded audio. Note that this is the normalized sum of all audio channels loaded for the given frame range. To preserve interactive speed, the audio data is not rendered into the timeline until that section of the frame range is played. You can turn on **Scrubbing** , in the **Audio** menu, to force the entire frame range to be loaded immediately. Also, if **Scrubbing** is on, audio will play during scrubbing, and during single frame stepping. See Section [4.6](#46-audio) for further details on Audio in RV. + +##### In/Out Range Manipulation + +Note that on each end of the timeline magnifier, there are two triangular \`\`arrow” buttons. These are the in/out nudge buttons, and clicking on them will move the in or out point by one frame in the indicated direction. The in/out range displayed in the timeline can also be changed with the mouse, either by dragging the whole range (click down in the middle of the range), or by dragging one of the endpoints (click down on the endpoint). All these manipulations can be performed during playback. You can also set an in/out range by shift-dragging with the left-button in timeline magnifier. + +##### Configuration + +All the hotkeys mentioned in Table [4.5](#useful-timeline-hotkeys) are also relevant to the timeline magnifier. The timeline magnifier configuration menu is also a subset of the regular timeline menu (see Figure [4.12](#timeline-configuration-popup-menu) ), with additional items for setting the height of the audio waveform display. + +![24_magnifierMenuShot.jpg](../../images/rv-user-manual-24-rv-cx-magnifierMenuShot-23.jpg) + +Figure 4.14: + +Timeline Magnifier Configuration Popup Menu + +### 4.6 Audio + + +When playing back audio with an image sequence or movie file, RV can be in one of two modes: video locked to audio or audio locked to video. + +When a movie with audio plays back at its native speed, the video is locked to the audio stream. This ensures that the audio and video are in sync. + +If you change the frame rate of the video, the opposite will occur: the audio will be locked to the video. When this happens, RV will synthesize new audio based on the existing audio in an attempt to either stretch or compress the playback in time. When pushed to the limits, the audio synthesis can create artifacts (e.g. when slowing down or speeding up by a factor of 2 or more). + +RV can handle audio files with any sample rate and can re-sample on the fly to match the output sample rate required by the available audio hardware. The recommended formats are AIFF or WAV. Use of mp3 and audio-only AAC files is not supported. + +Audio settings can be changed using the items on the Audio Menu. Volume, time Offset, and Balance can be edited per source or globally for the session. The RV Preferences Audio tab lets you choose the default audio device and set the initial volume (as well as some other technical options that are rarely changed). + +For visualizing the audio waveform see Section [4.5.1](#451-timeline) . + +#### 4.6.1 Audio Preferences + +RV provides audio preferences in the Preferences dialog. The most important audio preference is the choice of the output device from those listed. In practice this will rarely change. Preferences also let you set the initial volume for RV. The option to hold audio open is for use on Linux installations where audio system support is poor (see the next section on Linux Audio.) The other preferences are there for fine tuning performance in extreme cases of marginal audio hardware or support - they will almost never change. + +RV offers a cross-platform output module choice called “Platform Audio”. This is based on Qt audio. “Platform Audio” does support the use USB based audio peripherals for playback (e.g. Behringer UCA 202) on all platforms. These usb audio devices would typically appear as “USB Audio CODEC” (“front:CARD=CODEC,DEV=0” on Linux) in the “Output Device” pull down menu when “Platform Audio” is selected. + +![25_audio_prefs_mac.png](../../images/rv-user-manual-25-rv-cx-audio-prefs-mac-24.png) + +Figure 4.15: Audio Preferences (macOS) + +On the macOS and Windows there is only a single entry in this menu. On Linux, however, there may be many. (See Appendix [E](rv-user-manual-chapter-e.md#e-rv-audio-on-linux) for details about Linux Audio). + +The Output Device, Output Channel Layout, Output Format, and Output Rate determine the sound quality and speakers (e.g. mono, stereo, 5.1 etc) to use. Typical output rates are 44100 or 48000 Hz and 32 bit float or 16 bit integer output format. This produced Audio CD or DAT quality audio. + +Global Audio Offset is the means by which audio data can be time shifted backwards or forwards in time. The effect of this preference is observable in the audio waveform display. For example, setting the value to 0.5 seconds will shift the audio data by 0.5 seconds. + +Device Latency allows you to correct for audio/video sync differences measured during playback. It is measured in milliseconds, and defaults to zero. The audio waveform rendered in RV is not affected by the value of this preference since it does not offset the audio data that is cached. + +The Device Packet Size and Cache Packet Size can be changed, but not all output modules support arbitrary values. The default values are recommend. The Min/Max Buffer Size determines how much audio RV will cache ahead of the display frame. Ideally these numbers are low. + +Keep Audio Device Open When Not Playing should usually be set to ON. There are very few circumstances in which it's a good idea to turn this off. When the value is OFF, RV may skip frames and audio when playback starts and can become unstable. On some linux distributions turning this OFF will result in no audio at all after the first play. + +Hardware Audio and Video Synchronization determines which clock RV will use to sync video and audio. When on, RV will use the audio hardware clock if one is available otherwise it will use a CPU timer in software. In most cases this should be left ON. RV can usually detect when the audio clock is unstable or inaccurate and switch to the CPU timer automatically. However if playback with audio appears jerky (even when caching is on) it might be worth turning it off. + +Scrubbing On By Default (Cache all Audio) determines if audio scrubbing is on when RV starts. + +PreRoll Audio on Device Open generally improves the consistency of RV’s playback across different Linux machines and audio devices. It influences the overall AV sync lag, so expect to see different in AV sync readings when the feature is enabled versus turned off. In either case, the AV sync lag can be corrected via the Device Latency preference. Note that this feature is Linux only and available only for the Platform Audio module. It defaults to turned off. + +#### 4.6.2 Audio on Open RV for Linux + +Linux presents special challenges for multimedia applications and audio is perhaps the worst case. RV audio works well on Linux in many cases, but may be limited in others. RV supports special configuration options so that users can get the best audio functionality possible within the limitations of the vintage and flavor of Linux being used. See the Appendix [E](rv-user-manual-chapter-e.md#e-rv-audio-on-linux) for complete details. + +#### 4.6.3 Multichannel Audio + +In RV, the audio output module “Platform Audio” will support multichannel channel audio playback for devices that allow it. This would include six, eight or more channel layouts for surround sound speaker systems like 5.1 or 7.1. The list of available channel layouts for a chosen output device will be listed in the first pulldown menu for the “Output Format and Rate” setting. + +The list of all possible channel layouts that RV supports is described in Appendix [J](rv-user-manual-chapter-j.md#j-supported-multichannel-audio-layouts) . + +#### 4.6.4 Correcting for AV Sync Delay + +The “Device Latency” preference is intended to be used to compensate for any positive or negative AV sync measured during playback. To correct for an AV sync lag, first measure the delay with an AV sync meter. Then input the number from the meter into the Device Latency preference. + +**Please note:** The AV sync measurement can be influenced by the following audio preferences or playback settings: Hardware Audio Video Sync, PreRoll Audio on Device Open, and video/audio cache settings. + +To generate a sync flash sequence for use in measuring the AV sync at a particular frame rate, the following RV command line can be used. This example generates a 500 frame sequence with an audio bleep/video flash at intervals of 1sec at 24 FPS: + +``` +rv syncflash,start=1,end=500,interval=1,fps=24.movieproc +``` + +### 4.7 Caching + + +RV has a three state cache: it's either off, caching the current in/out range, or being used as a look-ahead (also known as a ring) buffer. + +![26_e_timeline_cache.png](../../images/rv-user-manual-26-rv-cx-e-timeline-cache-25.png) + +Figure 4.16: Timeline Showing Cache Progress + +The region cache reads frames starting at the in point and attempts to fill the cache up to the out point. If there is not enough room in the cache, RV will stop caching. The region cache can be toggled on or off from the Tools menu or by using the shift-C hot key. + +![27_ase_region_cache.png](../../images/rv-user-manual-27-rv-cx-ase-region-cache-26.png) + +Figure 4.17: Region Cache Operation With Lots of Memory + +![28_region_cache_no_room.png](../../images/rv-user-manual-28-rv-cx-region-cache-no-room-27.png) + +Figure 4.18: Region Cache Operation During Caching With Low Memory + +Look-ahead caching can be activated from the Tools menu or by using the meta-l hot key. The look-ahead cache attempts to smooth out playback by pre-caching frames right before they are played. If RV can read the files from disk at close to the frame rate, this is the best caching mode. If playback catches up to the look-ahead cache, playback will be paused until the cache is filled or for a length of time specified in the Caching preferences. At that point playback will resume. + +![29_look_ahead_cache.png](../../images/rv-user-manual-29-rv-cx-look-ahead-cache-28.png) + +Figure 4.19: Look-Ahead Cache Operation + +RV caches frames asynchronously (in the background). If you change frames while RV is caching it will attempt to load the requested frame as soon as possible. + +If the timeline widget is visible, cached regions will appear as a dark green stripe along the length of the widget. The stripe darkens to blue as the cache fills. The progress of the caching can be monitored using the timeline. On machines with multiple processors (or cores) the caching is done in one or more completely separate threads. + +**Note** that there is usually no advantage to setting the lookahead cache size to something large (if playback does not overtake the caching, a small lookahead cache is sufficient, and if it does, you probably want to use region caching anyway). + +### 4.8 Color, LUTs, and CDLs + + +RV provides users with fine grained color management and can support various color management scenarios. See [7.1](rv-user-manual-chapter-seven.md#rv-pixel-pipeline) for detailed technical information about RV's color pipeline. Without adding any nodes the default graph in RV supports three LUTs and two CDLs per file, an overall display LUT, and has a number of useful color transforms built-in. You can load LUTs and CDLs using the File → Import menu (Display, Look, File, and Pre-Cache items), or you can drag and drop the files onto the RV window. Smart drop targets will allow you determine how the LUT or CDL will be applied. Note that there is no CDL slot for the display by default. See chapter [8](rv-user-manual-chapter-eight.md#8-using-luts-in-rv) for more information about using LUTs and [9](rv-user-manual-chapter-nine.md#9-using-cdls-in-rv) for using CDLs in RV. + +RV's color transforms are separated into two menus. The Color menu contains transforms that will be applied to an individual source (whichever source is current in the timeline) and the View menu contains transforms that will be applied to all of the sources. This provides the opportunity to bring diverse sources (say Cineon Log files, QuickTime sRGB movies, and linear-light Exr's) all into a common working color space (typically linear) and then to apply a common output transform to get the pixels to the display. RV's built in hardware conversions can handle Cineon Log, sRGB, Viper Log and other useful transforms. + +### 4.9 Stereo + +RV supports playback of stereoscopic source material. RV has two methods for handling stereo source material: first any source may have multiple layers, and RV will treat the first two video layers of a source as left and right eyes for the purpose of stereo display. Left and right layers do not need to be the same resolution or format because RV always conforms media to fit the display window; Second, RV supports stereo QuickTime movies (taking the first two video tracks as left and right eyes) and multi-view EXR files. RVIO can author stereo QuickTime movies and multi-view EXR files as well, so a complete stereo publishing and review pipeline can be built with these tools. See the section on [12](rv-user-manual-chapter-twelve.md#12-stereo-viewing) for more information about how stereo is handled. + +### 4.10 Key and Mouse Bindings + +RV has many built-in shortcuts. You can learn about RV’s hotkeys via RV’s Help menu. + +![Help menu](../../images/rv-hotkeys-help-menu-01.png) + +From the Utilities section of the Help menu, select “Describe…” or “Describe Key Binding…” to see an explanation within RV of what certain hotkeys do. + +![Describe options in RV](../../images/rv-hotkeys-describe-options-rv-02.png) + +![Describe options](../../images/rv-hotkeys-describe-options-03.png) + +Menu items with hotkeys also display the hotkey on the right side of the menu item. + +![Hotkeys](../../images/rv-hotkeys-hotkeys-04.png) + +If you’d like to see a list of all of RV’s current key bindings, select “Show Current Bindings” from the Help menu. Below is also a list of RV’s hotkeys (note that capital and lowercase letters are different hotkeys): + +| Hotkey | Action | +| --- | --- | +| F1 (Fn + F1 on Mac) | Toggle Menu Bar Visibility | +| F2 (Fn + F2 on Mac) | Toggle Heads-Up Timeline | +| F3 (Fn + F3 on Mac) | Toggle Timeline Magnifier | +| F4 (Fn + F4 on Mac) | Toggle Heads-Up Image Info | +| F5 (Fn + F5 on Mac) | Toggle Heads-Up Color Inspector | +| F6 (Fn + F6 on Mac) | Toggle Wipes | +| F7 (Fn + F7 on Mac) | Toggle Heads-Up Info Strip | +| F8 (Fn + F8 on Mac) | Toggle Heads-Up External Process Progress | +| ~ | Toggle Timeline | +| ` | Toggle Fullscreen Mode | +| ! | Set image scale to 1:1 on presentation device | +| * | Apply Random Luminance LUT | +| ( | Cycle Image Stack Backwards | +| ) | Cycle Image Stack Forwards | +| [ | Set In Point | +| ] | Set Out Point | +| \| | Set In/Out Range From Surrounding Marks | +| \ | Reset In/Out Points | +| , | Set Frame Increment to -1 (reverse) | +| . | Set Frame Increment to 1 (forward) | +| < | Go to Matching Frame of Previous Source | +| > | Go to Matching Frame of Next Source | +| ? | Show Help Options | +| Space | Toggle Play | +| 1 | Scale 1:1 | +| 2 | Scale 2:1 | +| 3 | Scale 3:1 | +| 4 | Scale 4:1 | +| 5 | Scale 5:1 | +| 6 | Scale 6:1 | +| 7 | Scale 7:1 | +| 8 | Scale 8:1 | +| A | Toggle Real-Time Playback | +| a | Show Alpha Channel | +| B | Edit Display Brightness | +| b | Show Blue Channel | +| C | Toggle Region Caching | +| c | Normal Color Channel Display | +| D | Toggle Display LUT | +| e | Edit Current Source Relative Exposure | +| F | Enter FPS Value From Keyboard | +| f | Frame Image in View | +| G | Set Frame Number Using Keyboard | +| g | Show Green Channel | +| h | Edit Current Source Hue | +| i | Toggle Heads-Up Image Info | +| k | Edit Current Source Contrast | +| L | Toggle Cineon Log to Linear Conversion | +| l | Show Image Luminance | +| M | Cycle Matte Opacity | +| m | Toggle Mark At Frame | +| n | Toggle Nearest Neighbor/Linear Filter | +| P | Toggle Ping/Pong Playback | +| p | Toggle Premult Display | +| q | Close Session | +| R | Force Reload of Current Source | +| r | Show Red Channel | +| S | Edit Current Source Saturation | +| T | Toggle Current Luminance LUT | +| t | Toggle Heads-Up Timeline | +| v | Enter Display Gamma | +| W | Fit Window to Pixels | +| w | Resize Window to Fit | +| X | Flop Image | +| Y | Flip Image | +| y | Edit Current Source Gamma | +| Alt + f (Option + f on Mac) | Set image scale fit image width on presentation device | +| Alt + l (Option + l on Mac) | Rotate Image 90deg Counter-Clockwise | +| Alt + n (Option + n on Mac) | Turn On Nudge Keys | +| Alt + r (Option + r on Mac) | Rotate Image 90deg Clockwise | +| Alt + s (Option + s on Mac) | Turn On Stereo Keys | +| Alt + left arrow (Option + left arrow on Mac) | Go to Previous Marked Frame | +| Alt + right arrow (Option + right arrow on Mac) | Go to Next Marked Frame | +| Keypad-enter (Fn + enter/return on Mac) | Set Frame Number Using Keyboard | +| Home (Fn + left arrow on Mac) | Go to Beginning of In/Out Range | +| End (Fn + right arrow on Mac) | Go to End of In/Out Region | +| Page-up (Fn + up arrow on Mac) | Set In/Out to Next Marked Range | +| Page-down (Fn + down arrow on Mac) | Set In/Out to Previous Marked Range | +| Ctrl + e (Cmd + e on Mac) | Export Quicktime Movie | +| Ctrl + f (Cmd + f on Mac) | Frame Image Width | +| Ctrl + i (Cmd + i on Mac) | Add Source | +| Ctrl + l (Cmd + l on Mac) | Toggle Look-Ahead Caching | +| Ctrl + m (Cmd + m on Mac) | Cycle Mattes | +| Ctrl + o (Cmd + o on Mac) | Open File | +| Ctrl + p (Cmd + p on Mac) | Toggle Presentation Mode | +| Ctrl + q (Cmd + q on Mac) | Close Session | +| Ctrl + s (Cmd + s on Mac) | Save Session | +| Ctrl + v (Cmd + v on Mac) | Edit Global Audio Volume | +| Ctrl + w (Cmd + w on Mac) | Close Session | +| Ctrl + left arrow (Cmd + left arrow on Mac) | Set In/Out to Previous Marked Range | +| Ctrl + right arrow (Cmd + right arrow on Mac) | Set In/Out to Next Marked Range | +| Ctrl + up arrow (Cmd + up arrow on Mac) | Expand In/Out to Neighboring Marked Ranges | +| Ctrl + down arrow (Cmd + down arrow on Mac) | Contract In/Out from Neighboring Marked Ranges | +| Left arrow | Move Back One Frame | +| Right arrow | Step Forward 1 Frame | +| Up arrow | Toggle Forward/Backward Playback | +| Down arrow | Toggle Play | +| Tab | Toggle Heads-Up Timeline | +| Shift + home (Shift + Fn + left arrow on Mac) | Reset All Color | +| Shift + left arrow | Go to Previous View | +| Shift + right arrow | Go to Next View | + +#### Stereo Hotkeys + +RV has a supplementary "stereo hotkeys" mode in which an additional set of stero-related hotkeys are active. You enter or leave the mode with Alt-s (Option-s on Mac).  While this mode is active, the following additional hotkeys are available: + +| Hotkey | Action | +| --- | --- | +| a | Anaglyph Mode | +| d | Checked Mode | +| k | Scanline Mode | +| s | Side-by-Side Mode | +| p | Side-by-Side Stereo Mode | +| m | Mirrored Side-by-Side Stereo Mode | +| x | Stereo Mode Off (DEPRECATED -- use alt-s (or option-s on the mac)) | +| h | Hardware Stereo Mode | +| , | Left Eye Only Stereo Mode | +| . | Right Eye Only Stereo Mode | +| < | Left Eye Only Stereo Mode | +| > | Right Eye Only Stereo Mode | +| S | Swap Eyes | +| o | Edit Global Relative Stereo Offset| +| z | Horizontal Squeezed Stereo Mode | +| v | Vertical Squeezed Stereo Mode | +| r | Edit Global Right-Eye Stereo Offset | +| O | Turn OFF Stereo Display Mode (in Controller Window) | +| / | Reset Stereo Offsets | +| c | Edit Source/Clip Stereo Offset | +| R | Edit Source/Clip Right-Eye-Only Stereo Offset | + +#### Mouse Bindings + +Key and mouse bindings as well as menu bar menus are loaded at run time. You can override and change virtually any key or mouse binding from a file called ~/.rvrc.mu if you need to. The bindings (and whole interface) that comes with RV are located at $RV_HOME/plugins/Mu/rvui.mu. Functions in this file can be called from ~/.rvrc.mu or overridden. + +To override bindings, copy the file $RV_HOME/scripts/rv/rvrc.mu to ~/.rvrc.mu. + +| Alt | Ctrl | Shift | 1 | 2 | 3 | Wheel | Function | +| ------- | -------- | --------- | ----- | ----- | ----- | --------- | ---------------------- | +| | | | ↓ | | | | Toggle Play | +| | | | | ↓ | | | Toggle Play Direction | +| | | | ⇄ | | | | Scrub Frames | +| | | | | | | ⇄ | Scrub Frames | +| | • | | | | | ⇄ | Scrub Frames 10x | +| • | • | | | | | ⇄ | Scrub Frames 100x | +| | • | | ⇄ | | | | Zoom | +| | | | | ↻ | | | Translate | +| • | | | ↻ | | | | Translate | +| • | • | | ↻ | | | | Translate | +| • | | | | ↻ | | | Translate (Maya style) | +| • | | | ⇄ | ⇄ | | | Zoom (Maya style) | +| | | • | ↓ | | | | Inspect Pixel | +| | | • | ↻ | | | | Average Pixels | +| | | | | | ↓ | | Pop-up Menu | + +• held, ⇄ drag left and right, ↓ push without drag, ↻ drag any direction. + +Mouse button 1 is normally the left mouse button and button 3 is normally the right button on two button mice. Button 2 is either the middle mouse button or activated by pushing the scroll wheel on mice that have them. + +**If you can't annotate with the tablet and stylus:** If you use various inputs to control RV, such as Wacom tablets, then sometimes there is an incompatibility with the events those inputs generate and Qt. Try turning ON *Treat Stylus Events as Mouse Events* from RV Preferences General tab. + +### 4.11 Preferences File + + +RV stores configuration information in a preferences file in the user home directory. Each platform has a different location and possibly a different format for the file. + +| OS | File Location | File Format | +| -------- | ------------------------------------------ | --------------- | +| macOS | ~/Library/Preferences/com.tweaksoftware.RV | Property List | +| Linux | ~/.config/TweakSoftware/RV.conf | Config File | +| Windows | %APPDATA%/TweakSoftware/RV.ini | INI File | + +Table 4.8: Preference File Locations + +### 4.12 Audio Settings + +The Output Module in the Audio tab under RV Preferences lists audio interfaces that RV can choose between to handle audio, and is specific to operating systems. Each operating system has its own platform specific option, but fairly recently we created a Qt based cross platform setting named Platform Audio. We encourage you to use this option for Output Module. + +The Output Format and Rate configuration is an important setting, and you should choose a reasonable format and rate for the audio contained in the sources you most commonly review. Most of the time this is 32-bit float and either 44.1 or 48 kHz. + +Using “Keep Audio Device Open When Not Playing” will help reduce slow down and pops when looping playback or when starting and stopping frequently. However, in some Linux distributions this can conflict with other applications using the audio system. Using “Hardware Audio and Video Synchronization” may help keep your media synced during playback, but could introduce small delays for the two systems to line up when starting playback. + +#### Audio Scrubbing + +Under the Audio menu is an option to enable Scrubbing. This can be turned on by default from the Audio tab of RV’s preferences. For animators that are doing frame by frame stepping through clips, Scrubbing for lip-sync and other types of critical audio event timing can be useful. + +#### Offset Playback Timing of Open RV audio + +On the Audio tab of RV’s preferences, you can offset the playback timing of all RV audio in Global Audio Offset. This setting exists in case your system has some kind of audio exclusive latency so that you cannot configure both audio and video latencies from the Video tab. + +#### Synchronizing Audio and Video + +When using RV to play an image sequence paired with an audio file you may find that RV appears to play your imagery faster (or slower) than the accompanying audio. This happens because the way RV works is based on assuming each source has a native frame rate and that audio files do not have a native frame rate, because they have no frames. If RV is unable to determine the frame rate of a source and no indication was given at load, then RV will use the Default FPS. This is set in the Preferences dialog on the General tab. + +The Default FPS setting is used by both RV and RVIO, and it normally is only used for sources that have no discernible native frame rate, such as in an image sequence (without FPS metadata) or audio-only sources. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-fourteen.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-fourteen.md new file mode 100644 index 000000000..225762fcb --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-fourteen.md @@ -0,0 +1,256 @@ +# Chapter 14 - Maximizing Performance + +RV is primarily a desktop playback program. As such, there are no guarantees about performance. That being said, there are a number of methods to tune performance for any particular hardware configuration and operating system. The most important parameters to tune for good performance in RV are: + +* The number of CPUs/cores available versus the number of reader threads used in RV +* The specific file format being read (not all are created equal) and possible filesystem requirements like striping or guaranteed bandwidth constraints +* The I/O method for each file format (if it has multiple methods) versus the type of mass storage (SAN, RAID, or stripped disks, etc) +* If the file format decoder is threaded, the number of threads used for decoding (and balancing that with the number of reader threads) +* A fast PCI bus and a recent GPU + +Most of these settings are available from either the caching preference pane or the rendering preference pane. + +![65_ase_render_prefs.jpg](../../images/rv-user-manual-65-rv-cx-ase-render-prefs-65.jpg) + +Figure 14.1: Render Preference Pane + +![66_se_caching_prefs.jpg](../../images/rv-user-manual-66-rv-cx-se-caching-prefs-66.jpg) + +Figure 14.2: Caching Preference Pane + +### 14.1 File I/O and Decoding Latency + + +When reading frames directly from disk, file I/O is often a huge bottleneck. If your frames live across a network connection (such as an NFS mount) the latency can be even greater. Ideally, if RV is playing frames without caching, those frames would be on a local disk drive, RAID, or SAN sitting on a fast bus. + +Part of the I/O process is decoding compressed image formats. If the decoding is compute intensive, the time spent decoding may become a bottleneck. If good playback performance off of disk is a requirement, you should choose a format that does not require extensive decoding (Cineon or DPX) or one that can be parallelized (EXR). + +As always, there is a tradeoff between file size and decoding time. If you have a very slow network, you might get better performance by using a format with complex expensive compression. If your computer is connected to a local high-speed RAID array or an SSD, then storing files that are easy to decompress but have larger file size may be better. + +#### 14.1.1 EXR, DPX, JPEG, Cineon, TARGA, and Tiff I/O Methods + +The OpenEXR, DPX, JPEG, Cineon, TARGA, and Tiff file readers all allow you to choose between a few different I/O methods. The best method to use depends on the context RV is being used in. The method can be selected either from the command line or from the preferences (under each of the file formats). The command line options -exrIOMethod, -dpxIOMethod, -jpegIOMethod, -cinIOMethod, -tgaIOMethod, and -tiffIOMethod, require a number which corresponds to the methods listed in Table [14.1](#io-methods) and an optional I/O chunk size in bytes. Currently only the asynchronous methods use the chunk size. + +| Method | Number | Description | +| ----------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Standard | 0 | Use the standard native I/O method on the platform. On Windows this uses the WIN32 API. On Linux/Mac the standard C++ I/O streams are used. The file may be decoded piecemeal. | +| Buffered | 1 | Use the standard I/O but prefer that the file system cache is used. Attempt to read the file in the largest chunk possible. | +| Unbuffered | 2 | Use the standard I/O but ask the kernel to bypass the file system cache. Attempt to read the file in the largest chunk possible. | +| Memory Mapped | 3 | Memory Map files using the native memory mapping API. | +| Asynchronous Buffered | 4 | Asynchronous I/O using the native API. The chunk size may need to be manually tuned. | +| Asynchronous Unbuffered | 5 | Asynchronous I/O using the native API and bypassing the file system cache. The chunk size may need to be manually tuned.| + +Table 14.1: I/O Methods + +On Windows, the default is to use the Asynchronous Unbuffered method; this method generally produces the best results over the network and acceptable performance from a local drive. + +Typically, the circumstance in which RV is used will dictate which method is optimal. + +When using multiple reader threads, asynchronous methods may not scale as well as the synchronous ones. + +#### 14.1.2 Multiple Reader Threads + +When caching to the region or look-ahead cache, multiple threads can be used to read and process the frames. This can have a profound impact on I/O speed for most formats. You can select the number of threads from the command line using -rthreads or via the preferences under Preferences → Caching (requires restarting RV to take effect). + +The optimal number of threads to use varies with file format, hardware, and network or storage. However a good rule of thumb is to use the number of cores minus one; this leaves one core to handle I/O overhead and other threads (like audio) which may need to run as well. In some cases using more threads may increase performance and in others it will decrease performance. + +The file format used can also have an impact on the number of threads. OpenEXR in particular has its own threaded decoder. It may be necessary to leave some cores free to decode only. + +With Quicktime movies, the codec becomes important when using multiple threads. For single frame codecs like Photo-JPEG using many threads is advantageous. For H.264 and similar inter-frame codecs, using a single thread is usually the best bet though internally the writer may create more threads. + +If your storage hardware has high latency (for instance network storage vs local disk) you may find that it makes sense to have more reader threads than cores. This is because the high latency means that most reader threads will sit in IO wait states and not compete for CPU cycles. In general, in network storage situations, it makes sense to start with a number of reader threads equal to cores-1 or cores-2 and then increase the number of reader threads until you see a drop in performance. Also note that it is difficult to test this kind of performance, since a modern operating system will cache pages read from the network or disk in a RAM filesystem cache. On Linux, you can clear the page cache by typing: + +``` +shell> echo 1 > /proc/sys/vm/drop_caches +``` + +As root. + +### 14.2 Internal Software Operations + + +Some operations occur in software in RV. For example, when you read images in at a reduced resolution, the image has to be resampled in software. When software operations are being performed on incoming images, it's a good idea to use caching. If direct from disk playback performance is important than these operations should be avoided: + +* Image resolution changes +* Pre-Cache LUT +* Color resolution changes (e.g. float to 8 bit int color) +* Cropping +* Channel remapping + +The use of cropping can either increase or decrease performance depending on the circumstances. + +### 14.3 RAM + +The fastest playback occurs when frames are cached in your computer's RAM. The more RAM you have, the more frames that RV can cache and the more interactive it becomes. By default RV will load images at their full bit depth and size. E.g. a 32 bit RGBA tiff will be loaded, cached and sent to the graphic card at full resolution and bit depth. This gives artists the ability to inspect images with access to the full range of color information and dynamic range they contain, and makes it possible to work with high-dynamic range imagery. + +However, for playback and review of sequences at speed users may wish to cache images with different settings so as to fit more frames into the available RAM. You increase the number of frames that will fit in the cache by having RV read the frames with reduced resolution. For example, reducing resolution by half can result in as many as four times the number of frames being cached. + +Similarly, reducing the color resolution can squeeze more frames into memory. For example a 1024x1024 4 channel 8-bit integer image requires 4Mb of memory internally while the same image as 16-bit floating point requires 8Mb and a 32-bit float image requires 16Mb. So by having RV reduce a 32-bit float image to an 8-bit image, you can pack four times the number of frames into memory without changing the image size. + +Not caching the Alpha channel of a 4 channel image will also reduce the memory footprint of the frames. You can tell RV to remap the image channels to R, G, B before caching (See [7.3.2](rv-user-manual-chapter-seven.md#732-channel-remapping) ). This may affect playback speed for other reasons, depending on your graphics card. You will need to experiment to determine if this works well on your system. + +#### 14.3.1 Pixel Buffer Objects (PBOs) and Prefetch + +RV has an optional method of uploading pixels to the graphics card (PBOs). Normally, this is the fast path for maximum bandwidth to the card. However, some combinations of driver and GPU may result in poor performance. You can turn off the use of PBOs in the preferences or from the command line if you suspect PBOs are causing a bottleneck. + +Prefetch is only useful in conjunction with PBOs. Prefetch will attempt to asynchronously upload upcoming frames to the card ahead of the display frame. This option can add another 20% to display performance in some cases and cause a negative impact in others. + +If you're using a particular image format frequently it's a good idea to test using PBOs and prefetch to see how they perform on your hardware. To do so: cache a number of frames using the region cache. Make sure realtime mode is NOT on (in other words use play-all-frames mode). Set the target FPS to something impossible like 500 fps. With the preferences render pane open try playing back the frames from cache and see how fast it can go. Turn on and off PBO and prefetch usage to see what happens. + +#### 14.4.1 24 FPS 2048x1556 10 Bit DPX From Disk + +To achieve 24 fps DPX playback from disk at least a two core CPU with a RAID or other device which can achieve minimum sustained bandwidth of 300+Mb/sec across the whole array and sufficient latency and I/O transactions per second. Using two or more reader threads (depending on the number of available cores) with the look-ahead cache on can result in 24 fps DPX playback. This configuration should be purely bandwidth limited meaning that more bandwidth would result in higher FPS. + +Not all configurations will work. You may need to experiment. + +#### 14.4.2 OpenEXR 24 FPS 2048x1556 3 Y-RY-BY 16 bit Floating Point B44 and B44A From Disk + +OpenEXR decoding benefits from more cores. For best results a 4 or 8 core machine is required. B44 2k full-aperture 4:2:0 sampled files are approximately 4Mb in size so they don't require as much bandwidth as DPX files of the same resolution. For 4:4:4 sampled or B44A with an alpha channel more bandwidth and cores may be required. Generally speaking you should have about as much bandwidth as similar DPX playback would require. + +When decoding EXR files, you have the option of setting both the number of reader threads in RV and the number of decoder threads used by the EXR libraries. The exact numbers depend on the flavor of B44/A files being decoded. + +#### 14.4.3 Individual Desktop Review + +For individual desktop review the best quality and economy is undoubtably the use of QuickTime movies encoded with the Photo-JPEG codec. RV can play these back and create them on all three platforms. The image quality is good and the interactivity with the file is excellent (unlike H.264 which is slower when accessed randomly). If large QuickTime files are needed, RV can use multiple threads to decode the QuickTime movies efficiently. Many workstations can handle playback of 2k and 1080p Photo-JPEG QuickTime movies from the desktop using RV. + +## 14.5 Optimizing Open RV Playback Performance + +RV has a number of features intended to make it possible to read 2k or greater directly from fast I/O devices. Because there are more than a few variables that determine I/O and decoding speed you should try to start with a simple set of parameters in RV and then adjust one at a time. If you adjust all of them at the same time it’s much harder to figure out a sweet spot. + +We will try and update this with more information when we can. + +### 14.5.1 Quick Start + + +RV is most often used on the artist desktop, so the default preferences are not configured for streaming I/O. The most important changes to make to enable streaming I/O are listed here. More detail and other options are described below: + +1. Turn on the **Lookahead Cache** +2. Increase the number of **Reader Threads** in the RV preferences (experiment to find the best number) +3. If necessary, try **alternate I/O Methods** in the per-format image preferences +4. Use **prefetch** (with or without PBOs on) found in the Preferences->Render section +5. On linux try running RV as root with **-scheduler SCHED_RR -priorities 99 99** (see below) to make playback soft real-time + +### 14.5.2 System Requirements + + +We don't have a fixed system which we can recommend. People have been using a variety of different setups to get streaming play back. However you will definitely benefit if you have: + +* Lots of cores + procs +* Lots of memory (6 Gb on a 64 bit machine is a good idea) +* 64 bit machine +* A recent GPU (doesn't always have to be a Quadro) +* A good motherboard +* Some kind of fast I/O device. e.g a RAID, ioXTREME, etc. (A single "fast" disk drive probably won't cut it) +* NVidia card. Cheapo 580 Quadro is pretty good, Newer is probably better. Newer Geforce cards are probably good too. + +**Streaming I/O Image Formats** + +Streaming will only work with these formats. If you manage to get it to work with something other than one of these you are a miracle worker. + +* DPX and Cineon -- especially 10 bit files +* JPEG +* TARGA (TGA) +* TIFF -- typically RAW tiff works best, no compression +* EXR or ACES + +### 14.5.3 Multiple Reader Threads + +It is necessary to use multiple reader threads to get any kind of fast I/O streaming. In order to do so you *must be using the Look-Ahead cache* . We recommend using the smallest possible value for the look ahead cache size. Some people have said that more than 1Gb (and sometimes A LOT more) works for them. Ideally this number is not more than 1Gb. + +If you don't have at least two cores available you will probably not get streaming play back . + +**Start with two threads** . Optimize for speed with two threads and the try increasing the number after that. The number of threads can be controlled from the preferences under the caching tab. You need to restart RV when you change the number of threads. + +The more cores and procs you have the better. The more memory you have the better. + +### 14.5.4 Multiple EXR decoding threads + +If you want to stream EXR files you may want to reserve some of your cores for decoding. The number of EXR decoding threads can be changed from the preferences under the formats tab. Try "automatic" first. + +### 14.5.5 I/O Methods + +Which I/O method to use is hard to determine without experimenting. You can change the I/O method from the preferences formats tab. The basic I/O methods are: + +* **Standard** (for some formats). This uses whatever is considered the standard or normal way to read these files. For example with EXR this uses the "normal" EXR I/O streams that come as part of the EXR libraries. +* **Buffered** . The data is "streamed" as a single logical read of all data. The file data is allowed to reside in the filesystem cache if the kernel decides to do so. The file data is decoded after all the data is read. +* **Unbuffered** . Similar to buffered except that RV provides a hint to the kernel that file data should not be put into the filesystem cache. In theory this could lead to faster I/O because a copy of the data is not created during reading. In practice we've never seen this really help. There's a lot of debate on the internet about whether Unbuffered I/O is useful or not. +* **Memory Mapped** . The file contents are mapped directly to main memory. This has the advantage that it may not be filesystem cached and the memory is easily reclaimed by the process when no longer needed. In some respects it is similar to the buffered method above. On windows this method can increase the speed of local I/O significantly compared to the buffered, unbuffered, and standard methods above. +* **Asynchronous Buffered** . Similar to buffered above, however, the kernel may provide the data to RV in some random order instead of waiting to assemble the data in order itself. In addition the low-level I/O chunk size can be used to tune the I/O to maximize bandwidth. This method is most useful on Windows. On Linux and Mac Async I/O is not as useful in the context of RV because RV manages decode and read concurrency itself. +* **Asynchronous Unbuffered** . Same as Asynchronous buffered, but a hint is provided to the kernel to omit storing the data in the filesystem cache if possible. This is the default method on Windows. + +### 14.5.6 Rules of Thumb + +* Use the **Prefetch** option in the preferences. This will double the amount of VRAM required, but can significantly increase the amount of bandwidth between RV and the graphics card. This can be especially important when viewing stereo. Ideally, **Pixel Buffer Objects (PBOs)** are also turned on. Modern graphics cards benefit from turning on PBOs. For older cards you should experiment with them on and off -- even some pre-fermi Quadro cards can get worse performance with them on. +* With NVIDIA quadro fermi and kepler GPUs (fermi 4000, 6000, kepler 4000, 5000, 6000) enable **Multithreaded GPU Uploads** for linux and windows. On Mac enable **Apple Client Storage.** +* On Windows: try **Asynchronous Unbuffered** first. The only other method which might produce good results is **Memory Mapped** . +* If you are on linux **memory mapping** may cause erratic play back (esp. with software RAID). **Unbuffered** or **buffered** , are the preferred methods. However, if it works for you, great. +* The **Unbuffered** method may be a placebo on linux. It appears to depend on the type of filesystem you are reading from. For example on ubuntu 10.04 with ext4 it seems to be identical to **buffered** . It may actually be counterproductive over NFS. +* For EXR *don't* use the **standard** method. **Unbuffered** is typically the best method if the file system supports it. +* EXR B44 images are ideally subsampled as 4:2:0. Also, keep in mind that B44/A must be 16 bit. PIZ, ZIP, and ZIPS encoded EXR are CPU intensive and may require more EXR decoder threads. +* If you have recent graphics card try setting the DPX and Cineon 10 bit display depth to **10 Bits/Pixel Reversed** in the format preferences. This is the fastest and most color preserving method of dealing with 10 bit data in RV. If you have a "30 bit" capable monitor like an HP dreamcolor you can additionally put the X server or Windows in 30 bit mode to get both high precision color and fast streaming this way. +* Displaying 10 bit DPX in 16 bit mode requires 2x the bandwidth from the I/O device AND to the graphics card that 8 bit mode does. If you can use the 10 bit mode for DPX/Cineon try 8 bit first. +* If you I/O device has big latency, you may need to increase the number of reader threads dramatically to amortize the delay. It some cases it may be beneficial to use more threads than you have cores. This can happen with network storage. +* Make sure RV's v-sync is not on at the same time that the driver's GL v-sync is on. +* DPX files which are written so that pixel data starts 4096 bytes into the file, are little-endian, and which use four channel 8 bit, 3 channel 10 bit, or 4 channel 16 bit, and which have a resolution width divisible by 8. +* For all speed tests, be sure you're in "Play All Frames" mode (Control men) as opposed to "Realtime". In this context, "Realtime" means RV will skip video frames in order to keep pace with the audio. +* Not all I/O methods are supported by all file systems. In particular, the **Unbuffered** I/O method may not be supported by the underly file system implementation. + +### 14.5.7 A Note on Testing and the Filesystem Cache + +All operating systems (that RV runs on) try to maximze IO throughput by holding some pages from the filesystem in memory. The algorithms can be quite hard to predict, but the upshot is that if you're trying to test realtime streaming IO performance this "assistance" from the OS can invalidate your numbers; to be clear, for testing, you want to ensure that you start each run with **none** of the sequence to be played in memory. One way to do this is with a very large test set. Say several sequences, each of several thousand frames. If the frames are big enough and you RAM is small enough, then swiching to new sequence for each testing run will ensure that no part of the new sequence is already in the filesystem cache. + +But a more certain way to ensure that none of your frames are in the memory, which also lets you use the same sequence over and over for testing, is to forcibly clear the filesystem cache before each testing run. On Linux you can run this command: + +``` + sudo echo 1 > /proc/sys/vm/drop_caches +``` + +On macOS, you can: + +``` + purge +``` + +### 14.5.8 Real-Time + +On Linux, you can tell RV to run as a real-time application. This mode enables the most stable possible playback on linux, especially if the machine as been set up with server time slice durations (which is often the case when the machine is tuned for maximum throughput). Ideally, RV runs with **more and smaller** time slices -- at least for display and audio threads. + +To start RV in this mode use: + +``` +rv -scheduler SCHED_RR -priorities 99 99 +``` + +RV linux will try and use either the FIFO or Round Robin scheduler in place of the normal linux scheduler in this case. In order to do so, it must have the capability CAP_SYS_NICE. This can be achieved in a number of ways, but currently we have only been able to make two of them work: running setuid root on the linux binary so it has root privileges or just running rv as root. Ideally, you can use setcap to mark the rv.bin binary on the filesystem so that it has only CAP_SYS_NICE so non-root users can run it real-time, but we have not been able to get this to work (probably because we don't really understand it yet). + +On the Mac, RV is a real-time app by default and does not require any special privileges. + +On Windows, RV elevates its priorities as high as possible without admin privileges. + +NOTE: When RV runs with higher priorities, this is referring to only two of its threads: the display thread and the audio thread. Neither of these threads do much computational work. They are both usually blocked. So you shouldn't need to worry about RV consuming too many kernel resources. + +### 14.5.9 Refresh Rate and V-Sync + + +The biggest hurdle to making playback absolutely smooth is to recognize the effects of playback FPS coupled with the monitor refresh rate. For example, it’s typically the case that an LCD monitor will have a refresh rate of ~60Hz by default (i.e. it refreshes 60 times a second). Playing 24 FPS material on a 60Hz monitor will result in something similar to a 3/2 pull down. In order to get smoothest playback the ideal refresh rate would be 48 or 72Hz or some other multiple of 24. Also, be aware that a "60Hz" monitor may actually be 59.88Hz which means that even 30 FPS material will not play back perfectly smoothly. You need 59.88 / 2 (29.94) FPS for best results. + +#### 14.5.9.1 Multi-monitor Systems + +Systems with multiple attached monitors have another problem: the monitor that RV is playing on may not be the monitor that it’s syncing to. If that happens the play back can become very irregular. On linux for example, the driver can only sync to one monitor -- it can't change the sync monitor once RV has started. On linux you can change which monitor the driver uses for sync by setting the environment variable **__GL_SYNC_DISPLAY_DEVICE.** Here's a relevant passage from nvidia's driver README: + +> When using __GL_SYNC_TO_VBLANK with TwinView, OpenGL can only sync to oneof the display devices; this may cause tearing corruption on the displaydevice to which OpenGL is not syncing. You can use the environment variable __GL_SYNC_DISPLAY_DEVICE to specify to which display device OpenGL shouldsync. You should set this environment variable to the name of a displaydevice; for example "CRT-1". Look for the line "Connected displaydevice(s):" in your X log file for a list of the display devices presentand their names. You may also find it useful to review [Chapter 10, *Configuring TwinView*](http://ru.download.nvidia.com/freebsd/180.44/README/chapter-10.html) "Configuring Twinview," and the section on Ensuring Identical Mode Timings in [Chapter 16, *Programming Modes*](http://ru.download.nvidia.com/freebsd/180.44/README/chapter-16.html) . + +#### 14.5.9.2 RV's V-Sync versus driver V-Sync + +When playing 24 frames/second (24Hz) media on a monitor with a refresh rate of 60Hz (60 frames per second) you can only emulate the timing of frames being played on a 24Hz movie projector. It’s not possible to literally get the same timing. The reason is simple math: you can’t evenly divide 60 by 24. + +RV tries to get around this issue by playing back at as close to the device rate as possible (in this case 60Hz) and by trying to spread out 24 frames over the 60 actual frames in a visually pleasing way. If you were to look at the timing of the frames you would see that some frames are played back 3 times in a row and others only 2. + +The only way around this problem is to match the output device rate to the media rate in such a way that the device rate is evenly divisible by the media rate. + +Do not run RV with both the driver's GL v-sync on and RV's. This will almost guarantee bad playback. Use one or the other. You may want to experiment to see if one results in better timing than the other on your system. You can find these settings as follows + +* In RV Preferences under the Rendering tab, there is a "Video Sync" checkbox. +* In the Nvidia-settings GUI there is an openGL 'Sync to VBlank' checkbox. + +Nvidia recommends using the driver v-sync and disabling RV's v-sync if possible. On Linux, RV tries to detect which monitor the driver is using for sync and warn you if it’s not the one RV is playing on (it outputs an INFO message in the shell or console window). In presentation mode, RV shows a message box if the presentation device is not the sync device on linux. diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-g.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-g.md new file mode 100644 index 000000000..d930ce431 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-g.md @@ -0,0 +1,145 @@ +# G - Rising Sun Research CineSpace .csp File Format + +This is RSR's spec of their file format: + +The cineSpace LUT format contains three main sections. + +### Header + +This section contains the LUT identifier and the LUT type, 3D or 1D. + +It is made up of the first two (2) valid lines in the file. See Notes below for a definition of a valid line. + +A 3D LUT: + +``` +CSPLUTV100 +3D +``` + +or a 1D (channel) LUT: + +``` +CSPLUTV100 +1D +``` + +### Metadata + +This section contains metadata not defined by the CSP format itself. RV will ignore everything here except a line containing a conditioningGamma. For example: + +``` +BEGIN METADATA +conditioningGamma=0.4545 +END METADATA +``` + +The conditioningGamma value is applied (in GLSL) to both the input lattice points and the input data to the LUT (so does not change the mathematical “meaning” of the LUT). Especially for LUTs expecting linear input, this can compensate for the fact that, when the LUT is applied in hardware the even spacing of the lattice points can cause artifacts. In particular, we've found that a conditioningGamma value of 0.4545 allows a ACES-to-ACESlog LUT applied in RV (in HW) to match the results of applying the same LUT in Nuke (in SW). + +### 1D preLUT data + +This section is designed to allow for unevenly spaced data and also to accommodate input data that maybe outside the 0.0 <-> 1.0 range. Each primary channel, red, green and blue has each own 3 line entry. The first line is the number of preLUT data entries for that channel. The second line is the input and the third line is the mapped output that will then become the input for the LUT data section. + +It is made up of the valid lines 3 to 11 in the LUT. See Notes below for a definition of a valid line. + +Examples … + +Map extended input (max. 4.0) into top 10% of LUT + +``` +11 +0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 4.0 +0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 +11 +0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 4.0 +0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 +11 +0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 4.0 +0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 +``` + +Access LUT data via a gamma lookup Red channel has gamma 2.0 Green channel has gamma 3.0 but also has fewer points Blue channel has gamma 2.0 but also has fewer points + +``` +11 +0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 +0.0 0.01 0.04 0.09 0.16 0.25 0.36 0.49 0.64 0.81 1.0 +6 +0.0 0.2 0.4 0.6 0.8 1.0 +0.0 0.008 0.064 0.216 0.512 1.0 +6 +0.0 0.2 0.4 0.6 0.8 1.0 +0.0 0.04 0.16 0.36 0.64 1.0 +``` + +### LUT data + +This section contains the LUT data. The input stimuli for the LUT data is evenly spaced and normalized between 0.0 and 1.0. All data entries are space delimited floats. For 3D LUTs the data is red fastest. + +It is made up of the valid lines 12 and onwards in the LUT. See Notes below for a definition of a valid line. + +Examples … + +Linear LUT with cube sides R,G,B = 2,3,4 (ie. a 2x3x4 data set) + +``` +2 3 4 +0.0 0.0 0.0 +1.0 0.0 0.0 +0.0 0.5 0.0 +1.0 0.5 0.0 +0.0 1.0 0.0 +1.0 1.0 0.0 +0.0 0.0 0.33 +1.0 0.0 0.33 +0.0 0.5 0.33 +1.0 0.5 0.33 +0.0 1.0 0.33 +1.0 1.0 0.33 +0.0 0.0 0.66 +1.0 0.0 0.66 +0.0 0.5 0.66 +1.0 0.5 0.66 +0.0 1.0 0.66 +1.0 1.0 0.66 +0.0 0.0 1.0 +1.0 0.0 1.0 +0.0 0.5 1.0 +1.0 0.5 1.0 +0.0 1.0 1.0 +1.0 1.0 1.0 +``` + +### Notes: + +All lines starting with white space are considered not valid and are ignored. Lines can be escaped to the next line with “\\” (but note that RV does not actually implement this part of the spec, so lines should not be broken in CSP files for RV). All values on a single line are space delimited. + +The first line must contain the LUT type and version identifier “CSPLUTV100” + +The second line must contain either “3D” or “1D”. + +The third valid line (after any METADATA section) is the number of entries in the red 1D preLUT. It is an integer. + +The fourth valid line contains the input entries for the red 1D preLUT. These are floats and the range is not limited. The number of entries must be equal to the value on the third valid line. + +The fifth valid line contains the output entries for the red 1D preLUT. These are floats and the range is limited to 0.0 <-> 1.0. The number of entries must be equal to the value on the third valid line. + +The sixth valid line is the number of entries in the green 1D preLUT. It is an integer. + +The seventh valid line contains the input entries for the green 1D preLUT. These are floats and the range is not limited. The number of entries must be equal to the value on the sixth valid line. + +The eighth valid line contains the output entries for the green 1D preLUT. These are floats and the range is limited to 0.0 <-> 1.0. The number of entries must be equal to the value on the sixth valid line. + +The ninth valid line is the number of entries in the blue 1D preLUT. It is an integer. + +The tenth valid line contains the input entries for the blue 1D preLUT. These are floats and the range is not limited. The number of entries must be equal to the value on the ninth valid line. + +The eleventh valid line contains the output entries for the blue 1D preLUT. These are floats and the range is limited to 0.0 <-> 1.0. The number of entries must be equal to the value on the ninth valid line. + +The twelfth valid line in a “3D” LUT contains the axis lengths of the 3D data cube in R G B order. + +The twelfth valid line in a “1D” LUT contains the 1D LUT length + +The thirteenth valid line and onwards contain the LUT data. For 3D LUTs the order is red fastest. The data are floats and are not range limited. The data is evenly spaced. + +The LUT file should be named with the extension .csp \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-h.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-h.md new file mode 100644 index 000000000..ae249643c --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-h.md @@ -0,0 +1,3 @@ +# H - Crash Reporting + +RV includes google's crash reporting mechanism called breakpad on Linux. If RV crashes it will produce a file in /tmp with an extension of .dmp. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-i.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-i.md new file mode 100644 index 000000000..f05c2f704 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-i.md @@ -0,0 +1,168 @@ +# I - PySide example usage + +RV ships with PySide on all platforms. In this section, we present two simple examples of PySide usage. We also demonstrate how to access the RV session window in the second example. + +The first example, shown below, is a simple executable python/pyside file that uses RV py-interp. + +``` +#!/Applications/RV64.app/Contents/MacOS/py-interp + +# Import PySide classes +import sys +from PySide.QtCore import * +from PySide.QtGui import * + +# Create a Qt application. +# IMPORTANT: RV's py-interp contains an instance of QApplication; +# so always check if an instance already exists. +app = QApplication.instance() +if app == None: + app = QApplication(sys.argv) + +# Display the file path of the app. +print app.applicationFilePath() + +# Create a Label and show it. +label = QLabel("Using RV's PySide") +label.show() + +# Enter Qt application main loop. +app.exec_() + +sys.exit() +``` + +The second example, shown below, is a RV python package that uses pyside for building its UI with Qt widgets that control properties values on an RVLensWarp node. Note too that in this example, the current RV session QMainWindow obtained from rv.qtutils.sessionWindow() and we use it to change the session window's opacity with the “Enable” checkbox. + +This “PySide Example” can be loaded in RV through Preferences->Packages. + +``` +from PySide.QtCore import QFile +from PySide.QtGui import QDoubleSpinBox, QDial, QCheckBox +from PySide.QtUiTools import QUiLoader + +import types +import os +import math + +import rv +import rv.qtutils + +import pyside_example # need to get at the module itself + + +class PySideDockTest(rv.rvtypes.MinorMode): + "A python mode example that uses PySide" + + def checkBoxPressed(self, checkbox, prop): + def F(): + try: + if checkbox.isChecked(): + if self.rvSessionQObject is not None: + self.rvSessionQObject.setWindowOpacity(1.0) + rv.commands.setIntProperty(prop, [1], True) + else: + if self.rvSessionQObject is not None: + self.rvSessionQObject.setWindowOpacity(0.5) + rv.commands.setIntProperty(prop, [0], True) + except: + pass + return F + + + def dialChanged(self, index, last, spins, prop): + def F(value): + diff = float(value - last[index]) + if diff < -180: + diff = value - last[index] + 360 + elif diff > 180: + diff = value - last[index] - 360 + diff /= 360.0 + last[index] = float(value) + try: + p = rv.commands.getFloatProperty(prop, 0, 1231231) + p[0] += diff + if p[0] > spins[index].maximum() : + p[0] = spins[index].maximum() + if p[0] < spins[index].minimum() : + p[0] = spins[index].minimum() + spins[index].setValue(p[0]) + rv.commands.setFloatProperty(prop, p, True) + except: + pass + return F + + def spinChanged(self, index, spins, prop): + def F(value): + try: + rv.commands.setFloatProperty(prop, [p], True) + except: + pass + + def F(): + try: + p = spins[index].value() + commands.setFloatProperty(prop, [p], True) + except: + pass + + return F + + def findSet(self, typeObj, names): + array = [] + for n in names: + array.append(self.dialog.findChild(typeObj, n)) + if array[-1] == None: + print "Can't find", n + return array + + def hookup(self, checkbox, spins, dials, prop, last): + checkbox.released.connect(self.checkBoxPressed(checkbox, "%s.node.active"%prop)) + for i in range(0,3): + dial = dials[i] + spin = spins[i] + propName = "%s.warp.k%d" % (prop,i+1) + dial.valueChanged.connect(self.dialChanged(i, last, spins, propName)) + spin.valueChanged.connect(self.spinChanged(i, spins, propName)) + last[i] = dial.value() + + + def __init__(self): + rv.rvtypes.MinorMode.__init__(self) + self.init("pyside_example", None, None) + + self.loader = QUiLoader() + uifile = QFile(os.path.join(self.supportPath(pyside_example, "pyside_example"), "control.ui")) + uifile.open(QFile.ReadOnly) + self.dialog = self.loader.load(uifile) + uifile.close() + + self.enableCheckBox = self.dialog.findChild(QCheckBox, "enableCheckBox") + + # + # To retrieve the current RV session window and + # use it as a Qt QMainWindow, we do the following: + self.rvSessionQObject = rv.qtutils.sessionWindow() + + # have to hold refs here so they don't get deleted + self.radialDistortDials = self.findSet(QDial, ["k1Dial", "k2Dial", "k3Dial"]) + + self.radialDistortSpins = self.findSet(QDoubleSpinBox, ["k1SpinBox", "k2SpinBox", "k3SpinBox"]) + + self.lastRadialDistort = [0,0,0] + + self.hookup(self.enableCheckBox, self.radialDistortSpins, self.radialDistortDials, "#RVLensWarp", self.lastRadialDistort) + + + def activate(self): + rv.rvtypes.MinorMode.activate(self) + self.dialog.show() + + def deactivate(self): + rv.rvtypes.MinorMode.deactivate(self) + self.dialog.hide() + +def createMode(): + "Required to initialize the module. RV will call this function to create your mode." + return PySideDockTest() +``` \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-j.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-j.md new file mode 100644 index 000000000..c119d367b --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-j.md @@ -0,0 +1,43 @@ +# J - Supported Multichannel Audio Layouts + +Multichannel audio devices are supported by RV audio output module choice “Platform Audio”. The “Platform Audio” choice is available on all RV platforms ie. OSX, Linux and Windows. + +On OSX, you might need to enable the multichannel (e.g. 5.1) capability of your audio device using the OSX utility “Audio Midi Setup”. + +The list of possible channel layouts that RV recognises is listed in the table below. + +Table J.1: +| | | +| ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Channel Layout** | **Layout and Speaker Description**
**FL = Front Left**
**FR = Front Right**
**FC = Front Center**
**LF = Lower Frequency/Subwoofer**
**BL = Back Left**
**BR = Back Right**
**SL = Side Left**
**SR = Side Right**
**BC = Back Center**
**FLC = Front Left of Center**
**FRC = Front Right of Center**
**LH = Left Height**
**RH = Right Height** | +| Mono | FC | +| Stereo | FL:FR | +| 3.1 | FL:FR:LF | +| Quadrophonic | FL:FR:BL:BR | +| 5.1 | FL:FR:FC:FL:SL:SR | +| 5.1 (Back) | FL:FR:FC:FL:BL:BR | +| 5.1 (Swap) | FL:FR:BL:BR:FC:FL | +| 5.1 (AC3) | FL:FC:FR:SL:SR:LF | +| 5.1 (DTS) | FC:FL:FR:SL:SR:LF | +| 5.1 (AIFF) | FL:BL:FC:FR:BR:LF | +| 6.1 | FL:FR:FC:LF:BL:BR:BC | +| 7.1 (SDDS) | FL:FR:FC:LF:SL:SR:FLC:FRC | +| 7.1 | FL:FR:FC:LF:SL:SR:BL:BR | +| 7.1 (Back) | FL:FR:FC:LF:BL:BR:SL:SR | +| 9.1 | FL:FR:FC:LF:BL:BR:SL:SR:LH:RH | +| 16 | | + + +Supported Multichannel Layouts + +Note that RV will mix down, mix up or reorder channels for any given media to match the intended output device channel layout format. + +For example, playing back 5.1 media to a stereo audio device will see the 5.1 audio channels mixed downed to two channels. + +Similarly, playing back stereo media to a 5.1 device will see the media's stereo FL and FR content mixed up to the 5.1 device's FL, FR and FC only. + +For the case where the media and device have the same channel count and speaker types but different layout e.g. for 5.1 media and 5.1 (AC3) device, the media's channel layout is reordered to match the device channel layout when RV reads the media. + +For the case where the media and device have the same channel count but non-matching channel/speaker types, the channel layout of media is passed to the device as is; for example 5.1 (Back) media and 5.1 device. + +The audio channel layout for any given media can be determined from RV's image info tool. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-k.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-k.md new file mode 100644 index 000000000..29c4e75e0 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-k.md @@ -0,0 +1,107 @@ +# K - Localizing media paths with RV_OS_PATH or RV_PATHSWAP + +## RV_OS_PATH Variables + +**RV_OS_PATH** variables work similarly to **RV_PATHSWAP** variables, but they can be used to adjust paths of media files (and LUTs etc) that are more arbitrary, and are specifically targeted at supporting multi-OS environments. + +For example, if your site supports Linux/Windows/OSX machines, and the same basic file system structure appears on all OSs but at different roots, setting a group of three environment variables on all machines will allow some interoperability. Suppose you have the following three "roots" under which your directory structure is identical on all OSs: + +| OS | Root | +| --- | --- | +| OSX | /shows | +| LINUX | /net/shows | +| WINDOWS | c:/shows | + +Then if you can ensure that all users have the following environment variables set, paths will automatically be converted on input to RV: + +| Variable Name | Variable Value | +| --- | --- | +| RV_OS_PATH_OSX | /shows | +| RV_OS_PATH_LINUX | /net/shows | +| RV_OS_PATH_WINDOWS | c:/shows | + +For example if RV is running on Windows and receives a path like "/net/shows/sw4/trailer.mov" it will convert it to "c:/shows/sw4/tralier.mov" before use. ( **Note:** UNC paths are also supported). + +If some of your production data appears in a separate hierachy, you can add additional variables to support exceptions. For example, suppose that all show data is stored as above, but reference data is stored separately under these "roots": + +| OS | Root | +| --- | --- | +| OSX | /ref | +| LINUX | /net/reference | +| WINDOWS | c:/global/reference | + +In that case, you can set another triplet of environment variables to allow for that exception: + +| Variable Name | Variable Value | +| --- | --- | +| RV_OS_PATH_OSX_REF | /ref | +| RV_OS_PATH_LINUX_REF | /net/reference | +| RV_OS_PATH_WINDOWS_REF | c:/global/reference | + +Some additional details about RV_OS_PATH variables: + +* If you only use two different OSs, you only need to specify the corresponding pairs of environment variables. +* As above, additional sets of environment variables are considered to refer to the same "root" if the portion following the OS name matches ("REF" in the above example). +* You can set any number of sets of environment variables. +* RV_OS_PATH variables affect all incoming filenames **except** those which contain RV_PATHSWAP variables. +* RV_OS_PATH variables do not affect outgoing filenames. +* If more than one match is found, the variable that matches the largest number of characters in the incoming path will be used. + +> **Note:** If you need more dynamic control over your path remapping, you can author an RV package to handle transforming your paths with the ' [incoming-source-path](../rv-manuals/rv-reference-manual/rv-reference-manual-chapter-five.md) '. + +> **Note:** Due to how environments propagate, it is highly recommended to restart your computer after defining an environment variable on your system. + +## Open RV PATHSWAP Variables + + +If you're comfortable with environment variables, **RV_PATHSWAP** variables can provide a way to share session files across platforms and/or studio locations. + +> **Note:** Due to the difficult nature involving changing file paths, we do not recommend RV_PATHSWAP variables unless absolutely necessary. + +Suppose that you're working on a project called 'myshow' in two locations, but with shared or mirrored data. Location 'Win' is windows-based and at that location all the media lives in paths that have names that start with `\\projects\myshow`. The other location, 'Lin', is linux-based and at that site all the media for myshow appears in paths that start with '/shows/myshow'. + +To localize the media you can define a site-wide environment variable in each location called RV_PATHSWAP_MYSHOW, but with the corresponding value for that site: + +At location 'Lin', + +``` +RV_PATHSWAP_MYSHOW = "/shows/myshow" +``` + +And at location 'Win', + +``` +RV_PATHSWAP_MYSHOW = "//projects/myshow" +``` + +(Note the forward slashes in the above windows path. The PATHSWAP variables operate on internal RV paths, and at this point backward slashes have been converted to forward slashes.) + +You can have any number of these variables (they just all need to start with "RV_PATHSWAP_" so you could have one per show, for example. But note that if the above path pattern holds for all your projects, you can localize them all at once with variables like this one: + +``` +RV_PATHSWAP_ROOT = "/shows" +``` + +Once RV is running in an environment with these variables, it'll look for them in incoming paths (in session file, on the command line, in rvlink URLs, etc), and add them to paths it writes into session files (and network packets between synchronized RVs). + +The up-shot is that a session file written at either site can be read at either site, and a sync session between sites can refer to the same media with the appropriate path for that site. + +And of course these same benefits apply to using RV at a single site, but on several different platforms. + +## Hand-written Session Files or RVLINK URLs + +If you're writing your own session files to feed to RV "by hand", note that the format RV expects is something like this: + +``` +string movie = "${RV_PATHSWAP_MYSHOW}/myseq/myshot/mymov.mov" +``` + +Similarly, a URL to play that meda could look like: + +``` +rvlink://${RV_PATHSWAP_MYSHOW}/myseq/myshot/mymov.mov +``` + +## Remote Sync + +RV automatically swaps the values of appropriate PATHSWAP variables in and out of the names of media transmitted across a sync connection, so once you have them set up, these variables can also make Remote Sync seamless across sites or platforms. diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-nine.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-nine.md new file mode 100644 index 000000000..f7535911b --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-nine.md @@ -0,0 +1,12 @@ +# Chapter 9 - Using CDLs in Open RV + +As discussed previously there are two default places to set ASC CDL properties in the RV node graph. One is as a file CDL on the RVLinearize node in the RVLinearizePipepline and the other is as a look CDL on the RVColor node in the RVLookPipeline. In the case of the RVLinearize node the CDL is applied before linearization occurs whereas in the case of the RVColor node the CDL s applied after linearization and linear color changes. + +**To assign a CDL file to the source:** + +* The Import menu under the File menu is used to assign CDL files to either the source’s Look or File pipelines. + +### 9.1 CDL File Formats + + +The types of files RV supports right now are Color Decision List (.cdl), Color Correction (.cc) and Color Correction Collection (.ccc) files. Color Correction Collection files can include multiple Color Corrections tagged by ids. We do not support reading the properties by id. Therefore the first Color Collection found in the file will be read and used. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-one.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-one.md new file mode 100644 index 000000000..379eda8a6 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-one.md @@ -0,0 +1,167 @@ +# Chapter 1 - Introduction + +### 1.1 Overview + + +RV and its companion tools, RVIO and RVLS have been created to support digital artists, directors, supervisors, and production crews who need reliable, flexible, high-performance tools to review image sequences, movie files, and audio. RV is clean and simple in appearance and has been designed to let users load, play, inspect, navigate and edit image sequences and audio as simply and directly as possible. RV's advanced features do not clutter its appearance but are available through a rich command-line interface, extensive hot keys and key-chords, and smart drag/drop targets. RV can be extensively customized for integration into proprietary pipelines. The RV Reference Manual has information about RV customization. + +This chapter provides quick-start guides to RV and RVIO. If you already have successfully installed RV, and want to get going right away, this chapter will show you enough to get started. + +### 1.2 Getting Started With Open RV + + +#### 1.2.1 Loading Media and Saving Sessions + +There are four basic ways to load media into RV, + +1. Command-line, +2. File open dialogs, +3. Drag/Drop, and +4. rvlink: protocol URL + +RV can load individual files or multiple files (i.e. a sequence) and it can also read directories and figure out the sequences they contain; you can pass RV a directory on the command-line or drag and drop a folder onto RV. RV's ability to read directories can be particularly useful. If your shots are stored as one take per directory you can get in the habit of just dropping directories into RV or loading them on the command line. Or you can quickly load multiple sequences or movies that are stored in a single directory. + +Some simple RV command line examples are: + +``` +shell> rv foo.mov +shell> rv [ foo.#.exr foo.aiff ] +shell> rv foo_dir/ +shell> rv . +``` + +and of course: + +``` +shell> rv -help +``` + +The output of the -help flag is reproduced in this manual in the chapter on command-line usage, Chapter [3](rv-user-manual-chapter-three.md#3-command-line-usage) . + +RV sessions can be saved out as .rv files using the File->Save menus. Saved sessions contain the default views, user-defined views, color setup, compositing setup, and other settings. This is useful for reloading and sharing sessions, and also for setting up image conversion, compositing, or editing operations to be processed by RVIO. + +#### 1.2.2 Caching + +If your image sequences are too large to play back at speed directly from disk, you can cache them into system memory using RV's *region cache* . If you are playing compressed movies like large H.264 QuickTime movies, you can use RV's *lookahead cache* to smooth out playback without having to cache the entire movie. If your IO subsystems can provide the bandwidth, RV can be used to stream large uncompressed images from disk. You can set the RV cache options from the Tools menu, using the hot keys \`\`Shift+C” and \`\`Ctrl-L” (\`\`Command+L” on Mac) for the region cache and lookahead cache respectively, or from the command line using \`\`-c” or \`\`-l” flags. Also see the Caching tab of the Preferences dialog. + +#### 1.2.3 Sources and Layers + +RV gives you the option to load media (image sequences or audio) as a *Source* or a *Layer* . A source is a new sequence or movie that gets added to the end of the default sequence of the RV session. Adding sources is the simplest way to build an edit in RV. Layers are the way that RV associates related media, e.g. an audio clip that goes with an EXR sequence can be added as a layer so that it plays back along with the sequence. Layers make it very simple to string together sequences with associated audio clips–each movie or image sequence can be added as source with a corresponding audio clip added as a layer (see soundfile commandline example above). RV's stereoscopic display features can interpret the first two image layers in a source as left and right views. + +#### 1.2.4 Open RV Views + +RV provides three default views, and the ability to make views of your own. The three that all sessions have are the Default Sequence, which shows you all your sources in order, the Default Stack, which shows you all your sources stacked on top of one another, and the Default Layout, which has all the sources arranged in a grid (or a column, row, or any other custom layout of your own design). In addition to the default views, you can create any number of Sources, Sequences, Stacks, and Layouts of your own. See [5](rv-user-manual-chapter-five.md#51-rv-session) for information about the process of creating and managing your own views. + +#### 1.2.5 Marking and Navigating + +RV's timeline (hit TAB or F2 to bring it up) can be *marked* to make it easy to navigate around an RV session. RV can mark sequence boundaries automatically, but you can also use the \`\`m” key to place marks anywhere on the timeline. Once a session is marked you can use hot keys to quickly navigate the timeline, e.g. \`\`control+right arrow” (\`\`command+right arrow'' on Mac) will set the in/out points to the next pair of marks so you can loop over that part of the timeline. If no marks are set, many of these navigation options interpret the boundaries between sources as “virtual marks”, so that even without marking you can easily step from one source to the next, etc. + +#### 1.2.6 Color + +RV provides fine grained control over color management. Subsequent sections of this manual describe the RV color pipeline and options with a fair amount of technical detail. RV supports file LUTs and CDLs per source and an overall display LUT as well as a completely customizable 'source setup' function (described in the RV Reference Manual). For basic operation, however, you may find that the built in hardware conversions can do everything you need. A common example is playing a QuickTime movie that has baked-in sRGB together with an EXR sequence stored as linear floating point. RV can bring the QuickTime into linear space using the menu command Color->sRGB and then the whole session can be displayed to the monitor using the menu command View->sRGB. + +LUTs can be loaded into RV by dragging and dropping them onto the RV window, through the File->Import menus and on the command line using the -flut, -llut, -dlut, and -pclut options. Similarly CDLs can be loaded into RV by dragging and dropping them onto the RV window, through the File->Import menus and on the command line using the -fcdl and -lcdl options. + +#### 1.2.7 Menus, Help and Hot Keys + +Help->Show Current Bindings will print out all of RV's current key bindings to the shell or console (these are also included in chapter X of this manual). RV's menus can be reached through the menu bar or by using the right mouse button. Menus items with hot keys will display the hot key on the right side of the menu item. Some hot keys worth learning right away are: + +* Space - Toggle playback +* Tab or F2 - Toggle Show Timeline +* 'i' - Toggle Show Info Widget +* '\`' - (back-tick) Toggle Full Screen +* F1 - Toggle Show Menu +* Shift + Left Click - Open Pixel Inspector at pointer +* “q” to quit RV (or close the current session) + + +#### 1.2.8 Parameter Editing and Virtual Sliders + +Many settings in RV, like exposure, volume, or frame rate, can be changed quickly using Parameter Edit Mode. This mode lets you use virtual sliders, the mouse wheel or the keyboard to edit RV parameters. Hot keys and Parameter Editing Mode allow artists to easily and rapidly interact with images in RV. It is worth a little practice to get comfortable using these tools. For example, to adjust the exposure setting of a sequence you can use any of the following techniques: + +1. Hit the 'e' key to enter exposure editing mode Then: +2. Click and drag left or right to vary the exposure, and then release the mouse button to leave the mode, +3. OR: Roll the mouse wheel to vary the exposure and then hit return to leave the mode. +4. OR: Hit return, type the new exposure value at the prompt, and hit return again (typing '.' or any digit also starts this text-entry mode) +5. OR: Use the '+' and '-' keys to vary the exposure and then hit return to leave the mode. + +Some advanced usage: + +* Use the 'r' 'g' 'b' keys to edit individual color channels. ('c' to return to editing all 3 channels.) Parameters that can be “unganged” in this way will display a 3-color glyph in the display feedback when you start editing. +* Hit the 'l' to lock (or unlock) slider mode, so that you can repeatedly set the same parameter ('ESC' to exit). +* The 'DEL' or 'BackSpace' key will reset the parameter to it's default value. +* When multiple Sources are visible, as in a Layout view, parameter sliders will affect all Sources. Or you can use 's' to select only the source under the pointer for editing. + +Some parameters in RV don't use virtual sliders; you can edit these directly by entering the new value, e.g. to change the playback frames per second: + +1. Hit Shift+F +2. Type in the new frame rate at the prompt and hit return + +Some other useful parameters and their hot keys: + +* Gamma - 'y' +* Hue - 'h' +* Contrast - 'k' +* Audio Volume - ctrl-'v' (command-'v' on Mac) + +#### 1.2.9 Preferences and Command Line Parameters + +RV's Preferences can be opened with the RV->Preferences menu item. These are worth exploring in some detail. They give you fine control over how RV loads and displays images, handles color, manages the cache, handles audio, etc. RV's preferences map to RV's command line options, so almost any option available at the command line can be set to a preferred default value in the Preferences. RV also has a -noPrefs command line flag so that you can temporarily ignore the preferences, and a -resetPrefs flag that will reset all preferences to their default values. The quickest way to take a look at all of RV's command line options is: + +``` +shell> rv -help +``` + +### 1.2.10 Customizing Open RV + +RV is built to be customized. For many users, this may be completely ignored or be limited to sharing startup scripts and packages created by other users. A package is a collection of script code (Mu or Python) and interface elements which can be automatically loaded into RV. A package can be installed site wide, per-show, or per-user and a command line tool (rvpkg) is included for package administration tasks. + +RV Packages are discussed in Chapter [10](rv-user-manual-chapter-ten.md#10-packages) . + +Customization is discussed in detail in the RV Reference Manual. The RV command API technical documentation can be browsed from Help->Mu Command API Browser. + +### 1.3 Getting Started with RVIO + +#### 1.3.1 Converting Sequences and Audio + +RVIO is a powerful pipeline tool. Like RV, the basic operation of RVIO is very simple, but advanced (and complex) operations are possible. RVIO can be used with a command line very similar to RV's, with additional arguments for specifying the output. Any number of sources and layers can be given to RVIO using the same syntax as you would use for RV. Some basic RVIO command line examples are: + +``` +shell> rvio foo.exr -o foo.mov +shell> rvio [ foo.#.exr foo.aiff ] -o foo.mov +shell> rvio [ foo_right.#.exr foo_left.#.exr foo.aiff ] \ + -outstereo -o foo.mov +``` + +And of course: + +``` +shell> rvio -help +``` + +RVIO usage is more fully described in Chapter [16](rv-user-manual-chapter-sixteen.md#16-rvio) . + +#### 1.3.2 Processing Open RV Session Files + +RVIO can also take RV session files (.rv files) as input. RV session files can contain composites, color corrections, LUTs, CDLs, edits, and other information that might be easier to specify interactively in RV than by using the command line. RV session files can be saved from RV and then processed with RVIO. For example + +``` +shell> rvio foo.rv -o foo_out.#.exr +``` + +When rvio operates on a session file, any of the Views defined in the session file can be selected to provide rvio's output, so a single session could generate any number of different output sequences or movies, depending on which of the session's views you choose. + +#### 1.3.3 Slates, Mattes, Watermarks, etc. + +RVIO uses Mu scripts to create slates, frame burn-in and other operations that are useful for generating dailies, client reviews and other outputs. These scripts are usable as is, but they can also be modified or replaced by users. Some examples are: + +``` +shell> rvio foo.#.exr -overlay watermark "For Client Review" 0.5 \ + -o foo.mov +shell> rvio foo.#.exr -leader simpleslate \ + "Tweak Films" \ + "Artist=Jane Doe" \ + "Shot=SC101_vfx_01" \ + "Notes=Lighter/Darker" \ + -o foo.mov +``` \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-seven.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-seven.md new file mode 100644 index 000000000..7178aff60 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-seven.md @@ -0,0 +1,574 @@ +# Chapter 7 - How a Pixel Gets from a File to the Screen + +RV has a well defined image processing pipeline which is implemented as a combination of software and hardware (using the GPU when possible). Figure [7.1](#rv-pixel-pipeline) shows the pixel pipeline. + +![46_pipeline_diagram_4_0.png](../../images/rv-user-manual-46-rv-cx-pipeline-diagram-4-0-45.png) + +Figure 7.1: RV Pixel Pipeline + +### 7.1 Image Layers + + +Each image source may be composed of one or more layers. Layers may come from multiple files, or a single file if the file format supports it or a combination of the two. For example a stereo source can be constructed from a left and right movie file; in that case each file is a layer. Alternately, layers may come from a single file as would be the case with a stereo QuickTime file or EXR images with left and right layers. + +An image source may have any number of layers. By default, only the first layer is visible in RV unless an operation exposes the additional layers. + +#### 7.1.1 Stereo Layers + +RV has a number of stereo viewing options which render image layers to a left and right eye image. The left and right eye images are both layers. RV doesn't require any specific method of storing stereo images: you can store them in a single movie file as multiple tracks, as multiple movie files, or as multiple image sequences. You can even have one eye be a completely different format than the other if necessary. Stereo viewing is discussed in Chapter [12](rv-user-manual-chapter-twelve.md#12-stereo-viewing) . + +### 7.2 Image Attributes + + +RV tries to read as many image attributes as possible from the file. RV may also add attributes to the image to indicate things like pixel aspect ratio, alpha type, uncrop regions (data and display windows) and to indicate the color space the pixels are in. The image info window in the user interface shows all of the relevant image attributes. + +Some of the attributes are treated as special cases and can have an effect on rendering. Internally, RV will recognize and use the **ColorSpace/Primary** attributes automatically. Other **ColorSpace** attributes are used by the default source setup package (See Reference Manual) to set file to linear properties correctly. For example, if the **ColorSpace/Transfer** attribute has the value “Kodak Log”, the default source setup function will automatically turn on the Kodak log to linear function for that source. + +Image attributes can be saved as a text file directly from the UI (File → Export → Image Attributes), viewed interactively with the Tools → Image Info widget, or using the rvls program from the command line. + +| Attribute | How It's Used | +| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| ColorSpace/Primaries | Documents the name of the primary space if it has one | +| ColorSpace/Transfer | Contains the name of the transfer function used to convert non-linear R G B values to linear R G B values. This is used by the source setup script as a default non-linear to linear conversion. | +| ColorSpace/Conversion | If the image is encoded as a variant of luminance + chroma this attribute documents the name of the color conversion (or space) required to convert to non-linear R G B values | +| ColorSpace/ConversionMatrix | If the luminance + chroma conversion matrix is explicitly given, this attribute will contain it | +| ColorSpace/Gamma | If the image is gamma encoded the correction gamma is stored here | +| ColorSpace/Black Point | Explicit value for Kodak log to linear conversion if it exists or related functions which require a black/white point | +| ColorSpace/White Point | | +| ColorSpace/Rolloff | Explicit value for Kodak log to linear conversion | +| ColorSpace/Red Primary | Explicit primary values. These are used directly by the renderer unless told to ignore them. | +| ColorSpace/Green Primary | | +| ColorSpace/Blue Primary | | +| ColorSpace/White Primary | | +| ColorSpace/AdoptedNeutral | Indicates adopted color temperature (white point). | +| ColorSpace/RGBtoXYZMatrix | Explicit color space conversion matrix. This may be used instead of the primary attributes to determine a conversion to the RGB working space. | +| ColorSpace/LogCBlackSignal | LogC black signal. Black will be mapped to this value. The default is 0. | +| ColorSpace/LogCEncodingOffset | Derived from camera parameters. | +| ColorSpace/LogCEncodingGain | Derived from camera parameters. | +| ColorSpace/LogCGraySignal | The value mapped to 18% grey. The default is 0.18. | +| ColorSpace/LogCBlackOffset | Derived from camera parameters. | +| ColorSpace/LogCLinearSlope | Derived from camera parameters. | +| ColorSpace/LogCLinearOffset | Derived from camera parameters. | +| ColorSpace/LogCCutPoint | Indirectly determines the final linear/non-linear cut off point along with other parameters. | +| | | +| ColorSpace/ICC Profile Name | The name and data of an embedded ICC profile | +| ColorSpace/ICC Profile Data | | +| PixelAspectRatio | Pixel aspect ratio from file | +| DataWindowSize | Uncrop parameters if they appeared in a file | +| DataWindowOrigin | | +| DisplayWindowSize | | +| DisplayWindowOrigin | | + +Table 7.1: + +Basic Special Image Attributes + +### 7.3 Image Channels + + +RV potentially does a great deal of data conversion between reading a file and rendering an image on your display device. In some cases, you will want to have control over this process so it's important to understand what's occurring internally. For example, when RV reads a typical RGB TIFF file, you can assume the internal representation is a direct mapping from the data in the file. If, on the other hand, RV is reading an EXR file with A, B, G, T, and Z channels, and you are interested in the contents of the Z channel, you will need to tell RV specifically how to map the image data to an RGBA pixel. + +To see what channels an image has in it and what channels RV has decided to use for display you can select Tools **→** Image Info in the menu bar. The first two items displayed tell you the internal image format. In some cases you will see an additional item called ChannelNamesInFile which may show not only R, G, B channels, but additional channels in the file that are not being shown. + +RV stores images using between one and four channels. The channels are always the same data type and precision for a given image. If an image file on disk contains channels with differing precision or data type, the reader will choose the best four channels to map to RGBA (or fewer channels) and a data type and precision that best conserves the information present in the file. If there is no particular set of channels in the image that make sense to map to an internal RGBA image, RV will arbitrarily map up to the first four channels in order. By default, RV will interpret channel data as shown in Table [7.2](#mapping-of-file-channels-to-display-channels) + +| # of Channels | Names | RGBA Mapping | +| ------------------ | ---------- | ---------------- | +| 1 | Y | YYY1 | +| 2 | Y, A | YYYA | +| 3 | R, G, B | RGB1 | +| 4 | R, G, B, A | RGBA | + +Table 7.2: Mapping of File Channels to Display Channels + +Default interpretation of channels and how they are mapped to display RGBA. \`\`1'' means that the display channel is filled with the value 1.0. \`\`Y'' is luminance (a scalar image). + +When reading an image type that contains pixel data that is not directly mappable to RGB data (like YUV data), most of RV's image readers will automatically convert the data to RGB. This is the case for JPEG, and related image formats (QuickTime movies with JPEG compression for example). If the pixel data is not converted from YUV to RGB, RV will convert the pixels to RGB in hardware (if possible). + +#### 7.3.1 Precision + +RV natively handles both integer and floating point images. When one of RV's image readers decides a precision and data type for an image, all of its channels are converted to that type internally. + +| Channel Data Type | Display Range | Relative Memory Consumption | +| --------------------- | ----------------- | ------------------------------- | +| 8 bit int | [0.0 , 1.0] | 1 | +| 16 bit int | [0.0 , 1.0] | 2 | +| 16 bit float | [ - inf , inf ] | 2 | +| 32 bit float | [ - inf , inf ] | 4 | + +Table 7.3: Characteristics of Channel Data Types + +RV lets you modify how the images are stored internally. This ability is important because different internal formats can require different amounts of memory (see Table [7.3](#characteristics-of-channel-data-types) ). In some cases you will want to reduce or increase that memory requirement to fit more images into a cache or for faster or longer playback. + +You can force RV to use a specific precision in the interface Color → Color Resolution menu. There are two options here: (1) whether or not to allow floating point and (2) the maximum bit depth to use. + +In the RV image processing DAG, precision is controlled by the Format node. There are two properties which determine the behavior: **color.maxBitDepth** and **color.allowFloatingPoint** . + +#### 7.3.2 Channel Remapping + +RV provides a few similar functions which allow you to remap image channels to display channels. The most general method is called Channel Remapping. + +When Channel Remapping is active, RV reads all of the channel data in an image. This may result in images with too many channels internally (five or more), so RV will choose four channels to map to RGBA. + +You specify exactly which channels RV will choose and what order they should be in. The easiest way to accomplish this is in the user interface. By executing Tools → Remap Source Image Channels..., you can type in the names of the channels you want mapped to RGBA separated by commas. Using the previous example of an EXR with A, B, G, R, Z channels and you'd like to see Z as alpha, you could type in R,G,B,Z when prompted. If you'd like to see the value of Z as a greyscale image, you could type in Z or Z,A if you want to see the alpha along with it. + +It is also possible to add channels to incoming images using Channel Remapping. If you specify channel names that do not exist in the image file, new channels will be created. This is especially useful if you need to add an alpha channel to a three channel RGB image to increase playback performance + +1 + +New channels currently inherit the channel data from the first channel in the image. If the data needs to be 1.0 or 0.0 in the new channel, use Channel Reorder to insert constant data. + + + +Channel Remapping is controlled by the ChannelMap node. The names of the channels and their order is stored in the **format.channels** property. Channel Remapping occurs when the image data in the file is converted into one of the internal image formats. + +Note that there is overlapping functionality between Channel Remapping and Channel Reordering (See [7.8.1](#781-channel-reorder) ) and Channel Isolation (See [7.8.2](#782-isolating-channels) ) which are described below. However, Channel Remapping occurs just after pixels are read from a file. Channel Reordering and Isolation occur just before the pixel is displayed and typically happen in hardware. Channel Remapping always occurs in software. + +### 7.4 Crop and Uncrop + + +Cropping an image discards pixels outside of the crop region. The image size is reduced in the process. This can be beneficial when loading a large number of cached images where only a small portion of the frame is interesting or useful (e.g. a rendered element). For some formats, RV may be able to reduce I/O bandwidth by reading and decoding pixels only within the crop region. + +![47_rv_rv-cxx98-release_crop.png](../../images/rv-user-manual-47-rv-cxx98-release-crop-46.png) + +Figure 7.2: + +Cropping Parameters. Note that (x1, y1) are coordinates. + +Note that the four crop parameters describe the bottom and top corners not the origin, width, and height. So a “crop” of the entire image would be (0 , 0) to ( *w* - 1 , *h* - 1) where *w* is the image width and *h* is the image height. + +Uncropping (in terms of RV) creates a virtual image which is typically larger than the input image + +2 + +Quicktime calls this same functionality “clean aperture.” The OpenEXR documentation refers to something similar as the data and display windows. + +. The input image is usually placed completely inside of the larger virtual image. + +![48_ase_uncrop_basic.png](../../images/rv-user-manual-48-rv-cx-ase-uncrop-basic-47.png) + +Figure 7.3: + +Uncrop Parameters + +It is also possible to uncrop an image to a smaller size (in which case some pixels are beyond the virtual image border) or partially in and out of the virtual uncrop image. This handles both the cases of a cropped render and an a render of pixels beyond the final frame for compositing slop. + +The OpenEXR format includes a display and data window. These are almost directly translated to uncrop parameters except that in RV the display window always has an origin of (0 , 0) . When RV encounters differing display and data window attributes in an EXR file it will automatically convert these to uncrop values. This means that a sequence of EXR frames may have unique uncrop values for each frame. + +Currently EXR is the only format that supports per-frame uncrop in RV. + +![49_uncrop_over_render.png](../../images/rv-user-manual-49-rv-cx-uncrop-over-render-48.png) + +Figure 7.4: + +Uncrop of Oversized Render + +RV considers the uncropped image geometry as the principle image geometry. Values reported relating to the width and height in the user interface will usually refer to the uncropped geometry. Wipes, mattes, and other user interface will be drawing relative to the uncropped geometry. + +### 7.5 Conversion to Linear Color Space + + +If an image format stores pixel values in a color space which is non-linear, the values should be converted to linear before any color correction or display correction is applied. In the ICC and EXR documentation, linear space is also called *Scene Referred Space.* The most important characteristic of scene referred space is that doubling a value results in twice the luminance. + +Although any image format can potentially hold pixel data in a non-linear color space, there are few formats which are designed to do so. Kodak Cineon files, for example, store values in a logarithmic color space. + +3 + +The values in Cineon files are more complex than stated here. Color channel values are stored as code values which correspond to *printing density* – not luminance – as is the case with most other image file formats. Furthermore, the conversion to linear printing density is parameterized and these parameters can vary depending on the needs of the user. See the Kodak documentation on the Cineon format for a more detail description. + +JPEG images may be stored in \`\`video'' space + +4 + +This could mean NTSC color space or something else! + +which typically has a 2.2 gamma applied to the color values for better viewing on computer monitors. EXR files on the other hand are typically stored in linear space so no conversion need be applied. + +If values are not in linear space, color correction and display correction transforms can still be applied, but the results will not be correct and in some cases will be misleading. So it's important to realize what color space an image is in and to tell RV to linearize it. + +#### 7.5.1 Non-Rec. 709 Primaries + +If an image has attributes which provide primaries, RV will use this information to transform the color to the standard REC 709 primaries RGB space automatically. When the white points do not differ, this is done by concatenating two matrices: a transform to CIE XYZ space followed by a transform to RGB 709 space. When the white points differ chromatic adaptation is used. + +#### 7.5.2 Y RY BY Conversion + +OpenEXR files can be in stored as Y RY BY channels. The EXR reader will pass these files to RV as planar images (three separate images instead of one image with three channels). RV will then recombine the images in hardware into RGBA. + +This is advantageous when one or more of the original image planes are subsampled. Subsampled image planes have a reduced resolution. Typically the chroma channels (in this case RY and BY) are subsampled. + +#### 7.5.3 YUV (YCbCr) Conversion + +Some formats may produce YUV or YUVA images to be displayed. RV can decode these in hardware for better performance. RV uses the following matrix to transform YUV to RGB: + +| | | | | | +| - | ----------- | ----------- | ----------- | - | +| | 1 | 0 | 1.402 | | +| | 1 | - 0.344136 | - 0.714136 | | +| | 1 | 1.772 | 0 | | +| | | *Y* | | | +| | | *U* | | | +| | | *V* | | | + +where +| | +| ------------------ | +| 0 ≤ _Y_ ≤ 1 | +| - 0.5 ≤ _U_ ≤ 0.5 | +| - 0.5 ≤ _V_ ≤ 0.5 | + +In the case of Photo-JPEG, the YUV data coming from a movie file is assumed to use the full gamut for the given number of bits. For example, an eight bit per channel image would have luminance values of [0, 255]. For Motion-JPEG a reduced color gamut is assumed. + +On hardware that cannot support hardware conversion, RV will convert the image in software. You can tell which method RV is using by looking at the image info. If the display channels are YUVA the image is being decoded in hardware + +5 + +Conversion of YUVA to RGBA in hardware is an optimization that can result in faster playback on some platforms. + +. On Linux, this option must be specified when RV is started using the -yuv flag. + +6 + +Note that the color inspector will convert these values to normalized RGBA. + +#### 7.5.4 Log to Linear Color Space Conversion + +When RV reads a 10 bit Cineon or DPX file, it converts 10 bit integer pixel values into 16 bit integer values which are typically in a logarithmic color space. In order to correctly view one of these images, it is necessary to transform the 16 bit values into linear space. RV has two transforms for this conversion. You can access the function via the Color menu from the menu bar. For standard Cineon/DPX RV uses the default Kodak conversion parameters. + +For Shake users, RV produces the same result as the default parameters for the LogLin node. Alternately, if the image was created with a Grass Valley Viper FilmStream camera, you should use the Viper log conversion. + +The Kodak Log conversion can produce values in range [0, ~13.5]. To see values outside of [0,1] use the exposure feature to stop down image. A value of -3.72 for relative exposure will show all possible output values of the Kodak log to linear conversion. + +#### 7.5.6 File Gamma Correction + +If the image is stored in \`\`video'' or \`\`gamma'' space, you can convert to linear by applying a gamma correction. RV has three gamma transforms: one for linearization, one for display correction, and one in the middle for color correction. The gamma color correction can be activated via the Color → File Gamma menu items. When an image is stored in gamma space, it typically is transformed by the formula *c* 1 γ where γ is the gamma value and *c* is the channel value. When transforming back to linear space it's necessary to apply the inverse ( *c* γ ). RV's file gamma applies *c* γ while the color correction gamma and the display gamma apply *c* 1 γ (in all cases γ refers to the value visible in the interface). + +#### 7.5.7 sRGB to Linear Color Space Conversion + +If an image or movie file was encoded in sRGB space with the intention of making it easy to view with non-color aware software, RV can convert it to linear on the fly using this transform. In addition, some file LUTs are created to transform imagery from file space to sRGB space directly. If you apply one of these LUTs to an incoming image as a file LUT in RV you can then using the sRGB to linear function to linearize the output pixels. + +The sRGB to linear function can be activated via Color → sRGB and is stored per-source like the file gamma above or log to linear. + +RV uses the following equation to go from sRGB space to linear: + +*c**linear* = + +| | | +| --------------------------------- | --------------------- | +| *c**sRGB*12.92 | *c**sRGB* ≤ *p* | +| (*c**sRGB* + *a*1 + *a*)2.4 | *c**sRGB* > *p* | + +where + +*p* = 0.04045 + +*a* = 0.055 + +#### 7.5.8 Rec. 709 Transfer to Linear Color Space Conversion + +Similar to sRGB, The Rec. 709 non-linear transfer function is a gamma-like transform. High definition television imagery is often encoded using this color space. In order to view it properly on a computer monitor, it should be converted to linear and then to sRGB for display. The Rec. 709 to linear function can be activated via Color → Rec709 and is stored per-source like the file gamma, sRGB, or log to linear. + +RV uses the following equation to go from Rec. 709 space to linear: + +*c**linear* = + +| | | +| --------------------------------- | --------------------- | +| *c**709*4.5 | *c**709* ≤ *p* | +| (*c**709* +  0.0991.099)10.45 | *c**709* > *p* | + +where + +*p* = 0.081 + +#### 7.5.9 Pre-Cache and File LUTs + +RV has four points in its pixel pipeline where LUTs may be used. The first of these is the pre-cache LUT. The pre-cache LUT is applied in software, and as the name implies, the results go into the cache. The primary use of the pre-cache LUT is to convert the image colorspace in conjunction with a bit depth reformatting to maximize cache use. + +The file LUT and all subsequent LUTs are applied in hardware and are intended to be used as a conversion to the color working space (usually some linear space). However, the file LUT can also be used for as a transform from file to display color space if desired. + +See Chapeter [8](rv-user-manual-chapter-eight.md#8-using-luts-in-rv) for more information about how LUTs work in RV. + +#### 7.5.10 File CDLs + +In much the same way you can assign a File LUT to help linearize a file source into the working space in RV you can also use a file CDL. This CDL is applied before linearization occurs. There is also a look slot for CDL information described later. + +See Chapter [9](rv-user-manual-chapter-nine.md#9-using-cdls-in-rv) for more information about how LUTs work in RV. + +### 7.6 Color Correction + + +None of the color corrections affects the Alpha channel. For a good discussion on linear color corrections, check out Paul Haeberli's Graphica Obscura website [Graphica Obscura](http://www.graficaobscura.com/) . + +Color corrections are applied independently to each image source in an RV session. For example, if you have two movies playing in sequence in a session and you change the contrast, it will only affect the movie that is visible when the contrast is changed. (If you want to apply the same color correction to all sources, perform the correction in tiled mode: Tools → Stack, Tools → Tile.) + +#### 7.6.1 Luminance LUTs + +After conversion to linear, pixels may be passed though a luminance look up table. This can be useful when examining depth images or shadow maps. RV has a few predefined luminance LUTs: HSV, Random, and a number of contour LUTs. Each of these maps a luminance value to a color. + +#### 7.6.2 Relative Exposure + +RV's computes relative exposure like this: + +*c* × 2*exposure* + +where c is the incoming color. So a relative exposure of -1.0 will cause the color to be divided by 2.0. + +Relative exposure can alternately be thought of as increasing or decreasing the stop on a camera. So a relative exposure of -1.0 is equivalent to viewing the image as if the camera was stopped down by 1 when the picture was taken. + +To map an exact range of values (where 0 is always the low value) set the exposure to log21*max* where *max* is the upper bound. + +#### 7.6.3 Hue Rotation + +The unit of hue rotation is radians. RV's hue rotation is luminance preserving. A hue rotation of 2π will result in no hue change. + +The algorithm is as follows: + +* Apply a rotation that maps the grey vector to the blue axis. +* Compute the vector L that is perpendicular to the plane of constant luminance. +* Apply a skew transform to map the vector L onto the blue axis. +* Apply a rotation about the blue axis N radians where N is the amount of hue change. +* Apply a rotation that maps the blue axis back to the grey vector. + +RV computes luminance using this formula + +8 + +This is also the formula used for luminance display. + +: + +| | | | | | | +| ------ | ------ | ------ | --------- | - | -| +| *R**w* | *G**w* | *B**w* | *0* | | | +| *R**w* | *G**w* | *B**w* | *0* | | | +| *R**w* | *G**w* | *B**w* | *0* | | | +| *0* | *0* | *0* | *1* | | | +| | | | | | | +| | *R* | | | | +| | *G* | | | | +| | *B* | | | | +| | *1* | | | | + +where + +9 + +Weight values for R, G, and B are applicable in linear color space. Values used for determining luminance for NTSC video are not applicable in linear color space. + +*R**w* = 0.3086 +*G**w* = 0.6094 +*B**w* = 0.0820 + +#### 7.6.4 Relative Saturation + +RV applies the formula: + +| | | | | | | +| ------ | ------ | ------ | --------- | - | -| +| *R**w**(1 - s) + s* | *G**w**(1 - s)* | *B**w**(1 - s)* | *0* | | | +| *R**w**(1 - s)* | *G**w**(1 - s) + s* | *B**w**(1 - s)* | *0* | | | +| *R**w**(1 - s)* | *G**w**(1 - s)* | *B**w**(1 - s) + s* | *0* | | | +| *0* | *0* | *0* | *1* | | | +| | | | | | | +| | *R* | | | | +| | *G* | | | | +| | *B* | | | | +| | *1* | | | | + +where + +*R**w* = 0.3086 +*G**w* = 0.6094 +*B**w* *w* = 0.0820 + +and *s* is the saturation value. + +#### 7.6.5 Contrast + +RV applies the formula: + +| | | | | | | +| ------ | ------ | ------ | --------- | - | -| +| *1 + k* | *0* | *0* | *- k2* | | | +| *0* | *1 + k* | *0* | *- k2* | | | +| *0* | *0* | *1 + k* | *- k2* | | | +| *0* | *0* | *0* | *1* | | | +| | | | | | | +| | *R* | | | | +| | *G* | | | | +| | *B* | | | | +| | *1* | | | | + +where *k* is the contrast value. + +#### 7.6.6 Inversion + +RV applies the formula: + +| | | | | | | +| ------ | ------ | ------ | --------- | - | -| +| *-1* | *0* | *0* | *0* | | | +| *0* | *-1* | *0* | *0* | | | +| *0* | *0* | *-1* | *0* | | | +| *1* | *1* | *1* | *1* | | | +| | | | | | | +| | *R* | | | | +| | *G* | | | | +| | *B* | | | | +| | *1* | | | | + +#### 7.6.7 ASC Color Decision List (CDL) Controls + +ASC-CDL controls are as follows: + +| | | | +| ------ | ------ | ------ | +| *SOP* | = | *Clamp* ( *C**in* * *slope* + *offset* )*power* | + +where *slope* , *offset* , and *power* are per-channel parameters + +The CDL saturation is then applied to the result like so: + +*Clamp(* + +| | | | | | | +| ------ | ------ | ------ | --------- | - | -| +| *Rw(1 - s) + s* | *Gw(1 - s)* | *Bw(1 - s)* | *0* | | | +| *Rw(1 - s)* | *Gw(1 - s) + s* | *Bw(1 - s)* | *0* | | | +| *Rw(1 - s)* | *Gw(1 - s)* | *Bw(1 - s) + s* | *0* | | | +| *0* | *0* | *0* | *1* | | | +| | | | | | | +| | *SOPR* | | | | +| | *SOPG* | | | | +| | *SOPB* | | | | +| | *1* | | | | +) + +where + +*R**w* = 0.2126 +*G**w* = 0.7152 +*B**w* *w* = 0.0722 + +and *s* is the saturation when *s* ≥ 0 + +### 7.7 Display Simulation and Correction + + +After color corrections have been applied in linear space and before pixels are sent to the display device, they undergo display transformations. These transforms are intended to simulate the appearance of pixels on alternate display devices (like film) and to correct for any color transform that will be applied by the primary display device. In addition, RV provides a few tools to help visualize image pixel values in various ways. + +Unlike color corrections, display color corrections apply to all source material in an RV session. + +#### 7.7.1 Display and Look LUTs + +There are times when it's necessary to have two separate display LUTs — one which might be per-shot and one which is global. For example, this can happen when digital intermediate color work is being done on plates while the un-corrected plates are being worked on; a temporary LUT may be needed to simulate the \`\`look'' of the final result. + +There is a unique look LUT per source. There is a single display LUT for an RV session. + +RV applies the look LUT just before the display LUT. + +See Chapter [8](rv-user-manual-chapter-eight.md#8-using-luts-in-rv) for a more detailed explanation of usage and how to load a LUT into RV. + +#### 7.7.2 Display Gamma Correction + +This gamma correction is intended to compensate for monitor gamma. It is not related to File Gamma Correction, which is discussed in Section [7.5.6](#756-file-gamma-correction) . + +For a given monitor, there is usually one good value (e.g., 2.2) which when applied corrects the monitor's response to be nearly linear. Note that you should not use the Display Gamma Correction if your monitor has been calibrated with a gamma correction built in. + +If you are using the X Window System (Linux/Unix) or Microsoft Windows, the default is not to add a gamma correction for the monitor. + +On X Windows, this can be checked using the xgamma command. For example, in a shell if you type: + +``` +shell> xgamma +``` + +and you see: + +``` +-> Red 1.000, Green 1.000, Blue 1.000 +``` + +then your display has no gamma correction being applied. In this case you will want RV to correct for the non-linear response by setting the Display Gamma to correct for the monitor (e.g., 2.2). + +On macOS, things are more complex. Typically, a ColorSync profile is created for your monitor and this includes a gamma correction. However, the monitor may be corrected to achieve a non-linear response. The best bet on macOS is to calibrate the display in such a way that a linear response is achieved and don't use a Display Gamma other than 1.0 in RV. + +The Display Gamma can be set from the View menu. + +#### 7.7.3 sRGB Display Correction + +Most recently made monitors are built to have an sRGB response curve. This is similar to a Gamma 2.2 response curve, but with a more linear function in the blacks. RV supports this function directly for both input and output without the need to use a LUT. The sRGB display is a better default for most monitors than the display gamma correction above. + +RV's default rules for color set up use the sRGB display. RV also assumes by default that QuickTime movies and JPEG files are in sRGB color space unless they specifically indicate otherwise. This behavior can be overridden (see the Reference Manual). + +RV uses the following formula to convert from linear to sRGB: + +*c**sRGB* = + +| | | +| ----------------------------------------- | --------------------------- | +| 12.92*c**linear* | *c**linear* ≤ *q* | +| (1 + *a*)*c*1/2.4*linear* - *a* | *c**linear* > *q* | + +where + +*q* = 0.0031308 + +*a* = 0.055 + +The sRGB Display Correction can be set from the View menu. + +#### 7.7.4 Rec. 709 Non-Linear Transfer Display Correction + +If the display device is an HD television or reference monitor it may be naturally calibrated to the Rec. 709 color space. Similar to sRGB, Rec. 709 is a gamma-like curve. RV uses the following formula to convert from linear to Rec. 709: + +*c*709 = +| | | +| -------------------------------------- | --------------------------- | +| 4.5*c**linear* | *c**linear* ≤ *q* | +| 1.099*c**linear*0.45 - 0.099 | *c**linear* > *q* | + +where + +*q* = 0.018 + +#### 7.7.5 Display Brightness + +There is a final multiplier on the color which can be made after the display gamma. This is analogous to relative exposure discussed above. The Display Brightness will never affect the hue of displayed pixels, but can be used to increase or decrease the final brightness of the pixel. + +This is most useful when a display LUT is being used to simulate an alternate display device (like projected film). In some cases, the display LUT may scale luminance of the image down in order to represent the entire dynamic range of the display device. In order to examine dark parts of the frame, you can adjust the display brightness without worrying about any chromatic changes to the image. Note that bright colors may become clipped in the process. + +The Display Brightness can be set from the View menu. + +### 7.8 Final Display Filters + + +These operations occur after the display correction has been applied and before the pixel is displayed. Unlike color corrections, there is only a single instance of each of these for an RV session. So if you isolate the red channel for example, it will be isolated for all source material. + +#### 7.8.1 Channel Reorder + +If you have RGBA or fewer channels read into RV and you need to rearrange them for some reason, you can do so without using the general remapping technique described above. In that case you can use channel reordering which makes it possible to reorder RGBA channels and set one or more of the channels to 0.0 or 1.0. Channel Reordering can be accessed in the menu bar under View → Channel Order. On supported machines, this function is implemented in hardware (so it's very fast). + +This function can be useful if the image comes from a format with unnamed RGB channels which are not in order. Another useful feature of Channel reordering is the ability to flood one or more channels with the constant value of 1.0 or 0.0. For example to see the red channel as red without green and blue you could set the order to R000 (R followed by three zeros). To see the red channel as a grey scale image you could use the order RRR0. (Yes, that is exactly what channel isolation does! See below.) + +Channel reordering is controlled by the Display node. The **display.channelOrder** attribute determines the order. Channel reordering occurs when the internal image is rendered to the display. + +#### 7.8.2 Isolating Channels + +Finally, you can isolate the view to any single channel using the interface from the menu View → Channel Display. Since this is the most common operation when viewing channels, it is mapped to the keys \`\`r'', \`\`g'', \`\`b''and \`\`a'' by default (shows isolated red, green, blue, and alpha) and can be reset with the \`\`c'' key (show all color channels). You can also view luminance as a pseudo-channel with the \`\`l'' key. + +Channel isolation is controlled by the Display node. The **display.channelFlood** property controls which channel (or luminance) is displayed. Channel isolation occurs when the internal image is rendered to the display. + +#### 7.8.3 Out-of-range Display + +The Out-of-Range color transform operates per channel. If channel data is 0.0 or less, the channel value is clamped to 0.0. If the channel data is greater than 1.0, the channel data is clamped to 1.0. If the channel data is in the range [0.0, 1.0] the data is clamped to 0.5. + +The idea behind the transform is to display colors that are potentially problematic. If the pixels are grey, then they are \`\`safe'' in the [0.0, 1.0] range. If they are brightly colored or dark, they are out-of-range. + +0.0 is usually considered an out-of-range color for computer graphics applications when the image is intended for final output. + +Some compositing programs have trouble dealing with negative values. + +Note that HDR images will definitely display non-grey (bright) pixels when the out-of-range transform is applied. However, they probably should not display dark pixels. + +You can turn on Out-of-range Display from the View menu. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-seventeen.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-seventeen.md new file mode 100644 index 000000000..db46c5b45 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-seventeen.md @@ -0,0 +1,81 @@ +# Chapter 17 - RVLS + +The RVLS provides command line information about images, sequences, movie files and audio files. + +By default rvls with no arguments will list the contents of the current directory. Unlike its namesake ls, rvls will contract image sequences into a single listing. The output image sequences can be used as arguments to rv or RVIO + +1 + +You can use RVLS to create image sequence completions in bash or tcsh for rv and RVIO. See the reference manual for an example. + +. + +For more detailed information about a file or sequence the -l option can be used: this will show each file or sequence on a separate line with the image width and height, channel bit depth, and number of channels. For movie files and audio files, additional information about the audio like the sample rate and number of channels will be displayed. + +``` +shell> rvls -l + w x h typ #ch file + 623 x 261 16i 3 ./16bit.tiff + 1600 x 1071 8i 3 ./2287176218_5514bbc63a_o.jpg + 1024 x 680 8i 3 ./best-picture.jpg + 2048 x 1556 16f 3 ./sheet.hi.90.exr + 5892 x 4800 8i 1 ./BurialMount.psd + 64 x 64 8i 1 ./caust19.bw + 5892 x 4992 8i 1 ./common_sense.psd + 640 x 486 16i 3 ./colorWheel.685.cin + 640 x 480 8i 1 ./colour-bars-smpte-75-640x480.tiff +``` + +or to see certain files in a directory as a sequence: + +``` +shell> ls *.iff +render_maya.1.iff +render_maya.10.iff +render_maya.2.iff +render_maya.3.iff +render_maya.4.iff +render_maya.5.iff +render_maya.6.iff +render_maya.7.iff +render_maya.8.iff +render_maya.9.iff +shell> rvls *.iff +render_maya.1-10@.iff +``` + +It is also possible to list the image attributes using RVLS using the -x option. This will display the same attribute information that is displayed in RV's Image Info Widget: + +``` +shell> rvls -x unnamed_5_channel.exr +unnamed_5_channel.exr: + + Resolution 640 x 480, 5ch, 16 bits/ch floating point + Channels + PixelAspectRatio 1 + EXR/screenWindowWidth 1 + EXR/screenWindowCenter (0 0) + EXR/pixelAspectRatio 1 + EXR/lineOrder INCREASING_Y + EXR/displayWindow (0 0) - (639 479) + EXR/dataWindow (0 0) - (639 479) + EXR/compression ZIP_COMPRESSION + Colorspace Linear +``` + +Additional command line options can be see in table [17.1](#rvls-options) + +| | | +| ----------- | --------------------------------------------------------- | +| -a | Show hidden files | +| -s | Show sequences only (no non-sequence member files) | +| -l | Show long listing | +| -x | Show extended attributes and image structure | +| -b | Use brute force if no reader found | +| -nr | Do not show frame ranges | +| -ns | Do not infer sequences (list each file separately) | +| -min _int_ | Minimum number of files considered a sequence (default=3) | +| -formats | List image/movie formats | +| -version | Show rvls version number | + +Table 17.1: RVLS Options \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-six.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-six.md new file mode 100644 index 000000000..0463b6760 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-six.md @@ -0,0 +1,203 @@ +# Chapter 6 - Presentation Mode and Video Devices + +RV has a facility called presentation mode for use with machines with multiple attached display devices like projectors, second DVI/DisplayPort monitors or HDMI compatible stereo televisions. + +Presentation mode turns the main RV user interface into a control interface with output going to both it and a second video device. The secondary video device is always full screen. The primary use for presentation mode is multiple people viewing a session together. + +Typically a video device is set up once in the preferences and used repeatedly. Presentation mode can be turned on from the View → Presentation Mode menu item. It's also possible to pass command line arguments to RV to configure and start presentation mode automatically when it launches. + +### 6.1 Video Device Configuration + + +Video devices are configured from the preferences Video tab. The interface is in two parts: the module/device selection and the video configuration parameters. For each module/device combination there is a unique set of configuration parameters. + +Different devices will have different configuration parameters and some devices may not use all of the available ones. + +![42_ease_video_prefs.png](../../images/rv-user-manual-42-rv-cx-ease-video-prefs-41.png) + +Figure 6.1: + +Video Preferences + +Output Module + +A video output module can contain multiple devices. All of the devices in a module will have the choices for configuration parameters. For example, on a system with multiple monitors there will be a Desktop output module with a device for each connected monitor. In the video preferences window, you can also specify a default display profile for the module. + +Output Device + +The output devices are either numbered or named depending the module. The Desktop module uses the actual monitor name and manufacturer to identify them. In the video preferences you can specify a default display profile for the specific device or indicate that the device should use the default module profile. + +Output Video Format + +Video formats typically include a resolution and frequency. If the video format is not changeable by the user this field will display the output resolution of the device. In the video preferences a specific profile for the selected video and data format can be assigned. + +Output Data Format + +The data format indicates how the the pixels are to be presented to the device. This can include the numerical precision as well as color space (e.g. RGB or YCbCr). For devices that support stereo this is usually where stereo options are found. To use HDMI 1.4a stereo modes like Side-by-Side or Top-and-Bottom, you select the appropriate Data Format here. **PLEASE NOTE** : if you use a stereo data format here, then the Stereo mode on RV's View menu should be set to “Off”. + +Sync Method + +If the device as multiple options for synchronization these will be selectable under the sync method. For example, desktop devices typically have vertical sync on/off as an option. + +Sync Source + +If the synchronization can come from an external device it can be configured here. This option will usually change depending on the sync method. + +Use as Presentation Device + +If this is checked, the device will be used by presentation mode. Only one device can be selected as the presentation device. + +Output Audio to this Device + +If checked, audio will be routed to the device if it has audio capabilities. + +Incorporate Video Latency into Audio Offset + +If checked, the total latency as indicated in the Configure Latency dialog box will be applied to the audio offset – but only if audio is being played by the controller instead of the output device (i.e. Output Audio to this Device is not checked). This is primarily useful if the video device has a large buffer causing a significant delay between the controller's audio output and the video output. + +### 6.2 Display Profiles + + +Each video device configuration can have a unique display profile associated with it or can be made to use a default device or module profile. Profiles can be created and managed via View → Create/Edit Display Profiles.... Profiles are searched for in the RV_SUPPORT_PATH in the Profiles subdirectory (folder). + +Display profiles are snapshots of the view settings including a display LUT if present, the transfer function (sRGB, Rec.709, Gamma 2.2, etc), the primaries, the background, view channel ordering, stereo view modes, and dithering. If a custom nodes have been defined and are used in the display color pipeline than those will also be stored in the display profile. + +![43_e_profile_dialog.png](../../images/rv-user-manual-43-rv-cx-e-profile-dialog-42.png) + +Figure 6.2: + +Display Profile Manager + +The display profile manager can be started from the view menu. This is where profiles can be created and deleted. + +When a profile is created, the values for the profile are taken from the current display device or you can select another device at creation time. Most of the view settings for the current device are present under the View menu + +1 + +Custom or alternate nodes inserted into the RVDisplayPipelineGroup and their property values will also be stored in the profile if they are present. This makes it possible to use custom shaders, multiple display LUTs, or OpenColorIO nodes and have them be saved in the profile. + +. Newly created profiles are always written to the user's local Profile area. To share a profile with other user's, the profile file can be moved to a common RV_SUPPORT_PATH location. + +If a profile is already assigned to a device, the device name will appear next to the profile in the manager. + +By selecting a profile and activating the Apply button, you can set the profile on the current view device. Applying a profile does not cause it to be remembered between sessions. In order to permanently assign a profile use the Video tab in the preferences. + +### 6.3 Video Device Command Line Arguments + + +There are five arguments which control how presentation mode starts up from the command line: + +| | | +| ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| -present | Causes the program to start up in presentation mode | +| -presentAudio [0 or 1] | Enables audio output to the presentation device if 1 or turns off if 0 | +| -presentDevice MODULE/DEVICE | Forces the use of DEVICE from MODULE. Note that the forward slash character must separate the device and module names. | +| -presentVideoFormat format | Forces the use of format for the video format. The format is a string or substring of full description of the video format as the appear in the video module in the preferences. For example: “1080p” would match “1080p 24Hz”. The first match is used. | +| -presentDataFormat format | Forces the use of format for the data format. Like the video format above, the data format string is matched against the full description of the data formats as they appear in the video module in the preferences. For example: “Stereo” would match “Dual Stereo YCrCb 4:2:2”.| + +Table 6.1: + +Presentation Mode Command Line Arguments + +The command line arguments will override any existing preferences. + +### 6.4 Presentation Mode Settings + + +When the controller display mode is set to Separate Output and Control Rendering you can choose which elements of the user interface are visible on the presentation device. These can be turned on/off via the View → Presentation Settings menu. This includes not only things like the timeline and image info widget, but also whether or not the pointer location should be visible or not. In addition, you can show the actual video settings as an overlay on the display itself in order to verify the format is as expected. You can also control the display of feedback messages and remote sync pointers with items on this menu. The settings are retained in the preferences. + +If you want your custom-build widget to be rendered on the Presentation Device, your widget just needs to set the Widget class data member _drawOnPresentation to true. + +### 6.5 Platform Specific Considerations + + +#### 6.5.1 Linux Desktop Video Module Issues + +These issues apply only when using the desktop video module. + +The current state of the X server XRANDR extension and client library prevents us from implementing automatic resolution switching from RV. If your distribution has the xrandr binary available and installed, you can manually use that to force the presentation monitor into the proper resolution. + +When presentation mode starts up, RV will put the control window into a mode that allows tearing of the image in order to ensure that the presentation window will not tear. Be aware that the control window is no longer synced to a monitor. + +##### nVidia Driver + +RV will warn you if your presentation device is set to a monitor that the nVidia driver is not using for vertical sync. In that case you can continue, but tearing will probably occur if the attached monitors are not using identical timings. You can set the monitor which the driver syncs to using the __GL_SYNC_DISPLAY_DEVICE environment variable as per nVidia's driver documentation. See the nvidia-settings program to figure out the proper names (e.g. DPF-1 or CRT-1). + +nVidia recommends setting the driver V-Sync on and turning RV's V-Sync off if possible. If not, RV will attempt to sync using the appropriate GL extension. You can turn on/off RV's V-Sync via the checkbox under Preferences → Rendering. + +#### 6.5.2 Mac Desktop Video Module Issues + +On macOS it's not a known fact, but it appears that vertical sync is timed to the primary monitor. This is the monitor on which the menu bar appears. You can change this via the system display settings Arrangement tab. + +Ideally, the presentation device will be on the primary monitor. + +RV will configure the controller display to prevent interfering with the playback of the presentation monitor. The control device may exhibit tearing or other artifacts during playback. On some versions of macOS, once the controller has entered this mode, it cannot be switched back even after presentation mode has been exited. + +#### 6.5.3 Windows 7 Video Module Issues + +On Windows, like macOS, the vertical sync is somewhat of an unknown. However, it appears that like macOS, the primary monitor (the one with the start menu) is the monitor the sync is derived from. So ideally, use the primary monitor as the presentation device, but your milage may vary. + +### 6.6 HDMI Frame Packed Mode + + +Stereoscopic media can be displayed in Frame Packed (or “Frame Packing”) mode on any HDMI 1.4a-compliant device (AKA 3DTV). But in order for RV to make use of this display resolution (which is roughly a double-height HD frame), the monitor or other display device must have the appropriate resolution defined in advance. The timings from the HDMI 1.4a specification are shown in the following table: + +| | | | | +| ----------------- | -------------- | -------------- | ------------ | +| | | **Horizontal** | **Vertical** | +| **Active pixels** | **1080p 24Hz** | 1920 | 2205 | +| | **720p 60Hz** | 1280 | 1470 | +| **Front porch** | **1080p 24Hz** | 638 | 4 | +| | **720p 60Hz** | 110 | 5 | +| **Sync width** | **1080p 24Hz** | 48 | 5 | +| | **720p 60Hz** | 40 | 5 | +| **Back porch** | **1080p 24Hz** | 144 | 36 | +| | **720p 60Hz** | 220 | 20 | + +Table 6.2: + +HDMI 1.4a Frame Packed Video Timings + +An additional important number is the required “pixel clock” or “pixel frequency”, which should be 148.5 MHz (for either 1080p or 720p),. + +#### Linux + +Sample Xorg.conf lines for 1080p 24Hz (720p 60Hz commented out), using the timings from the table above: + +``` +Section "Monitor" +... +Modeline "1920x2205_24" 148.32 1920 2558 2602 2750 2205 2209 2214 2250 +hsync +vsync +#Modeline "1280x1470_60" 148.5 1280 1390 1430 1650 1470 1475 1480 1500 +hsync +vsync +... +EndSection +Section "Device" +... +Option "ModeValidation" "NoDFPNativeResolutionCheck" +Option "ExactModeTimingsDVI" "True" +... +EndSection +Section "Screen" +... +Option "metamodes" "DFP-0: 1920x1080_60 +0+0, DFP-2: 1920x2205_24 +1920+0" +... +EndSection +``` + +#### Mac + +On OSX, we have used the shareware utility SwitchResX to add a Frame Packed resolution. Once the 1920x2205 resolution is visible in the Display preferences, it will show up in the Output Video Format selector on the Video Preference for the appropriate output device. If this device is selected as the presentation device, the monitor will go in and out of this mode when presentation mode is turned on and off. + +It's possible that there are various limitations to this approach. Our testing was on a MacBook Pro with Nvidia GFX (9600M GT), and we have reports of this working on a MacPro with Nvidia GFX (Quadro 4000) and a MacPro with ATI GFX (Radeon HD 5770), although the latter required some adjustments. Also, we have no reports of this working on OSX 10.7 yet. In one case, we haven't been able to make this work with a MacBookPro running 10.7 with ATI GFX. So, bottom line is that it can be made to work in some cases, but we can't guarantee it. + +Here's a screengrab of the SwitchResX custom resolution dialog, showing our 1920x2205 mode: + +![44_ase_macCustomRes.jpg](../../images/rv-user-manual-44-rv-cx-ase-macCustomRes-43.jpg) + +#### Windows + +On Windows, you can use the NVIDIA control panel to create a custom resolution with the above timings. Go to “Change Resolution”, then “Custimize”, then “Create Custom Resolution”, then “Timing”, for the “Standard”, choose “Manual” and input the timings described above. Please note that RV cannot put the monitor in this Custom Resolution (the windows API does not expose the custom resolutions), so you need to choose the custom mode yourself (in the NVIDIA control panel, “Change Resolution” section) before you start RV. When RV starts, it will add the current (custom) mode to the Video Formats list in the Video Preferences tab, and you can select it there, then select the “Frame Packed” Data Format. + +Here's an example the timing setup that has worked for us: + +![45_ase_winCustomRes.jpg](../../images/rv-user-manual-45-rv-cx-ase-winCustomRes-44.jpg) \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-sixteen.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-sixteen.md new file mode 100644 index 000000000..ffaf2d546 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-sixteen.md @@ -0,0 +1,453 @@ +# Chapter 16 - RVIO + +RVIO is a command line (re)mastering tool–it converts image sequence, audio files, and movie files from one format into another including possible bit depth and image color and size resolution changes. RVIO can also generate custom slate frames, add per-frame information and matting directly onto output images, and change color spaces (to a limited extent). + +RVIO supports all of the same movie, image, and audio formats that RV does including the .rv session file. RV session files (.rv) can be used to specify compositing operations, split screens, tiling, color corrections, pixel aspect ratio changes, etc. + +*Table 16.1: rvio Options* + +| | | +| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| -o *string* | Output sequence or image | +| -t *string* | Output time range (default=input time range) | +| -tio | Output time range from rv session files in/out points | +| -v | Verbose messages | +| -vv | Really Verbose messages | +| -q | Best quality for color conversions (slower – mostly unnecessary) | +| -noRanges | No separate frame ranges (1-10 will be considered a file) | +| -rthreads *int* | Number of reader/render threads (default=1) | +| -wthreads *int* | Number of writer threads (default=same as -rthreads) | +| -formats | Show all supported image and movie formats | +| -iomethod *int* [_int_] | I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=3) and optional chunk size (default=61440) | +| -view *string* | View to render (default=defaultSequence or current view in rv file) | +| -leader ... | Insert leader/slate (can use multiple time) | +| -leaderframes *int* | Number of leader frames (default=1) | +| -overlay ... | Visual overlay(s) (can use multiple times) | +| -inlog | Convert input to linear space via Cineon Log->Lin | +| -insrgb | Convert input to linear space from sRGB space | +| -in709 | Convert input to linear space from Rec-709 space | +| -ingamma *float* | Convert input using gamma correction | +| -fileGamma *float* | Convert input to linear space using gamma correction | +| -inchannepmap ... | map input channels | +| -inpremult | premultiply alpha and color | +| -inunpremult | un-premultiply alpha and color | +| -exposure *float* | Apply relative exposure change (in stops) | +| -scale *float* | Scale input image geometry | +| -resize *int* [_int_] | Resize input image geometry to exact size on input (0 = maintain image aspect) | +| -resampleMethod *string* | Resampling method (area, linear, cubic, nearest, default=area) | +| -floatLUT *int* | Use floating point LUTs (1=yes, 0=no, default=1) | +| -flut *string* | Apply file LUT | +| -dlut *string* | Apply display LUT | +| -flip | Flip image (flip vertical) (keep orientation flags the same) | +| -flop | Flop image (flip horizontal) (keep orientation flags the same) | +| -yryby *int int int* | Y RY BY sub-sampled planar output | +| -yrybya *int int int int* | Y RY BY A sub-sampled planar output | +| -yuv *int int int* | Y U V sub-sampled planar output | +| -outparams ... | Codec specific output parameters | +| -outchannelmap ... | map output channels | +| -outrgb | same as -outChannelMap R G B | +| -outpremult | premultiply alpha and color | +| -outunpremult | un-premultiply alpha and color | +| -outlog | Convert output to log space via Cineon Lin->Log | +| -outsrgb | Convert output to sRGB ColorSpace | +| -out709 | Convert output to Rec-709 ColorSpace | +| -outgamma | Apply gamma to output | +| -outstereo *string* | Output stereo (checker, scanline, anaglyph, lumanaglyph, left, right, pair, mirror, hsqueezed, vsqueezed, default=separate) | +| -outformat *int string* | Output bits and format (e.g. 16 float -or- 8 int) | +| -outhalf | Same as -outformat 16 float | +| -out8 | Same as -outformat 8 int | +| -outres *int int* | Output resolution (image will be fit, not stretched) | +| -outfps | Output FPS | +| -codec *string* | Output codec (varies with file format) | +| -audiocodec *string* | Output audio codec (varies with file format) | +| -audiorate *float* | Output audio sample rate (default from input) | +| -audiochannels *int* | Output audio channels (default from input) | +| -quality *float* | Output codec quality 0.0 -> 1.0 (use varies with file format and codec default=0.900000) | +| -outpa* float* | Output pixel aspect ratio (e.g. 1.33 or 4:3, etc, metadata only) default=1:1 | +| -comment *string* | Output comment (movie files, default="") | +| -copyright *string* | Output copyright (movie files, default="") | +| -debug *string* | Debug category | +| -version | Show RVIO version number | +| -exrcpus *int* | EXR thread count (default=_platform dependant_) | +| -exrRGBA | EXR use basic RGBA interface (default=false) | +| -exrInherit | EXR guesses channel inheritance (default=false) | +| -exrIOMethod int [int] | EXR I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=0) and optional chunk size (default=61440) | +| -jpegRGBA | Make JPEG four channel RGBA on read (default=no, use RGB or YUV) | +| -jpegIOMethod int [int] | JPEG I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=0) and optional chunk size (default=61440) | +| -cinpixel *string* | Cineon/DPX pixel storage (default=RGB16) | +| -cinchroma | Use file chromaticity values (ignores them by default) | +| -cinIOMethod int [int] | Cineon I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=3) and optional chunk size (default=61440) | +| -dpxpixel string | DPX pixel storage (default=RGB16) | +| -dpxchroma | Use DPX chromaticity values (ignores them by default) | +| -dpxIOMethod int [int] | DPX I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=3) and optional chunk size (default=61440) | +| -tgaIOMethod int [int] | TARGA I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=2) and optional chunk size (default=61440) | +| -tiffIOMethod int [int] | TIFF I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=2) and optional chunk size (default=61440) | +| -init *string* | Override init script | +| -err-to-out | Output errors to standard output (instead of standard error) | +| -noprerender | Turn off prerendering optimization | +| -flags ... | Arbitrary flags (flag, or 'name=value') for Mu or Python | + +### 16.1 Basic Usage + +#### 16.1.1 Image Sequence Format Conversion + +RVIO's most basic operation is to convert a sequence of images from one format into another. RVIO uses the same sequence notation as RV to specify input and output sequences. For example: + +``` +shell> rvio foo.#.exr -o foo.#.tif +shell> rvio foo.#.exr -o foo.#.jpg +``` + +#### 16.1.2 Image sequence to QuickTime Movie Conversion + +RVIO can write out QuickTime movies. The default compression codec is PhotoJPEG with a default quality of 0.9. + +``` +shell> rvio foo.#.exr -o foo.mov +``` + +#### 16.1.3 Resizing and Scaling + +Rvio does high quality filtering when it resizes images. Output resolution can be specified explicitly, in which case RVIO will conform to the new resolution, padding the image to preserve pixel aspect ratio. + +``` +shell> rvio foo.1-100@@@@.tga -scale 0.5 -o foo.#.tga +shell> rvio foo.#.exr -outres 640 480 +``` + +Or you can use the -resize x y option, in which case scaling will occur in either dimension (unless the number specified for that dimension is 0). + +#### 16.1.4 Adding Audio to Movies + +RVIO uses the same layer notation as RV to combine audio with image sequences. Multiple audio sources can be mixed in a single layer. + +``` +shell> rvio [ foo.1-300#.tif foo.aiff ] -o foo.mov +shell> rvio [ foo.1-300#.tif foo.aiff bar.aiff ] -o foo.mov +``` + +### 16.2 Advanced Usage + +#### 16.2.1 Editing Sequences + +RVIO can combine multiple sequences and write out a single output sequence or movie. This allows you to quickly edit and conform. + +``` +shell> rvio foo.25-122#.exr bar.8-78#.exr -o foo.mov +shell> rvio [ foo.#.exr foo.aiff ] [ bar.#.exr bar.aiff ] \ + -o foobar.mov +``` + +Note that you can cut in and out of movie files as well: + +``` +shell> rvio [ foo.mov -in 101 -out 120 ] [ bar.mov -in 58 -out 123 ] -o out.mov +``` + +#### 16.2.2 Processing Open RV Session Files + +RV session files can be used as a way to author operations for processing with RVIO. Most operations and settings in RV can be used by RVIO. For example RV can be used in a compositing mode to do an over or difference or split-screen comparison. RV can also be used to set up edits with per source color corrections (e.g. a unique LUT for each sequences, exposure or color adjustments per sequences, an overall output LUT, etc.). + +``` +shell> rvio foo.rv -o foo.mov +``` + +Any View in the session file can be used as the source for the RVIO output: + +``` +shell> rvio foo.rv -o foo.mov -view "latest shots" +``` + +#### 16.2.3 Advanced Image Conversions + +RVIO has flags to handle standard colorspace, gamma, and log/lin conversions for both inputs and outputs + +``` +shell> rvio foo.#.cin -inlog -o foo.#.exr +shell> rvio foo.#.exr -o foo.#.cin -outlog +shell> rvio foo.#.jpeg -insrgb -o foo.#.exr +shell> rvio foo.#.exr -o foo.#.tiff -outgamma 2.2 +shell> rvio foo.#.exr -o foo.#.jpeg -outsrgb +shell> rvio foo.#.cin -inlog -o foo.mov -outsrgb \ + -comment "Movie is in sRGB space" +``` + +#### 16.2.4 LUTs + +RVIO can apply a LUT to input files and an output LUT. RVIO's command line only supports one file LUT and one display LUT. The file LUT will be applied to all the input sources before conversion and and the output LUT will be applied to the entire session on output. If you need to process a sequences with a different file LUT per sequence, you can do that by creating an RV session file with the desired LUTs and color settings to use as input to RVIO. + +``` +shell> rvio foo.#.cin bar.#.cin -inlog -flut in.cube \ + -dlut out.cube -o foobar.mov +``` + +#### 16.2.5 Pixel Storage Formats and Channel Mapping + +RVIO provides control over pixel storage (floating point or integer, bit depth, planar subsampling) and channel mapping. The planar subsampling options are particularly used to support OpenExr's B44 compression, which is a fixed bandwidth, floating point, high-dynamic range compression scheme. + +``` +shell> rvio foo.#.exr -outformat 8 int -o foo.#.tif +shell> rvio foo.#.exr -outformat 8 int -o foo.#.tif -outrgb +shell> rvio foo.#.cin -inlog -o foo..#.tiff -outformat 16 float +shell> rvio foo.#.exr -outformat 32 float -o foo.#.tif +shell> rvio foo.#.exr -codec B44A -yrybya 1 2 2 2 -outformat \ + 16 float +shell> rvio foo.#.exr -codec B44A -yryby 1 2 2 -outformat \ + 16 float -outchannelmap R G B +``` + +#### 16.2.6 Advanced QuickTime Movie Conversions + +RVIO uses ffmpeg for reading and writing. You can find out what codecs are available for reading and writing by using the "-formats" flag. This will also tell you the full name of the encoder, e.g. writing Motion JPEG requires "mjpeg" by ffmpeg. RVIO lets you specify the output codec and can collect parameters for the output from "outparams". Please examine the following two examples. The first is with the default settings for mjpeg's Motion JPEG writing, and the second is a popular command derived from using the ffmpeg binary directly. Something of the form `ffmpeg -i foo.%04d.tif -ac 2 -b:v 2000k -c:a pcm_s16be -c:v mjpeg -pix_fmt yuv420p -g 30 -b:a 160k -vprofile high -bf 0 -strict experimental -f mov outfile.mov`. + +``` +shell> rvio foo.#.tif -codec mjpeg -o foo.mov +shell> rvio foo.#.tif -codec mjpeg -audiocodec pcm_s16be \ + -outparams pix_fmt=yuv420p vcc:b=2000000 acc:b=160000 \ + vcc:g=30 vcc:profile=high vcc:bf=0 -o foo.mov +``` + +#### 16.2.7 Audio Conversions + +RVIO can operate directly on audio files and can also add or extract audio to/from QuickTime movies. RVIO provides flags to set the audio codec, sample rate, storage format (8, 16, and 32 bits) storage type (int, float), and number of output channels. RVIO does high quality resampling using 32 bit floating point operations. RVIO will mix together multiple audio files specified in a layer. + +``` +shell> rvio foo.mov -o foo.aiff +shell> rvio foo.mov -o foo.aiff -audiorate 22050 +shell> rvio [ foo.#.exr foo.aiff bar.wav ] -codec H.264 \ + -quality 1.0 \ + -audiocodec "Apple Lossless" -audiorate 44100 \ + -audioquality 1.0 -o foo.mov +``` + +#### 16.2.8 Stereoscopic and Multiview Conversions + +RV and RVIO support stereoscopic playback and conversions. RVIO can be used to create stereo QuickTime files or multiview OpenExr files (Weta's SXR files) by specifying two input layers and using the -outstereo flag. Stereo QuickTime movies contain multiple video tracks. RV interprets the fist two tracks as left and right views. RVIO will also render output using stereo modes specified in an RV session file–this allows you to output anaglyph images from stereo inputs or to render out scanline or alternating pixel stereo material. + +``` +shell> rvio [ foo_l.#.exr foo_r.#.exr foo.aiff ] -outstereo \ + -o stereo.mov +shell> rvio [ foo_left.#.exr foo_right.#.exr ] -outstereo \ + -o stereo.#.exr +``` + +Or you can specify an output stereo format: + +``` +shell> rvio [ foo_l.#.exr foo_r.#.exr foo.aiff ] -outstereo hsqueezed \ + -o stereo.mov +``` + +#### 16.2.9 Slates, Mattes, Watermarks, and Burn-ins + +RVIO supports script based creation of slates and overlays. The default scripts that come with RV can be used as is, or they can be customized to create any kind of overlay or slate that you need. Customization of these scripts is covered in the RV Reference Manual. RVIO has two command line flags to manage these scripts, -leader and -overlay. -leader scripts are used to create Slates or other frames that will be added to the beginning of a sequence or movie. -overlay scripts will draw on top of the image frames. Multiple overlays can be layered on top of the image, so that you can build up a frame with mattes, frame burn-in, bugs, etc. The scripts that come with RV include: + +- simpleslate +- watermark +- matte +- frameburn +- bug + +##### Simpleslate Leader + +Simpleslate allows you to build up a slate from a list of attribute/value pairs. It will automatically scale all of your text to fit onto the frame. It works like this: + +``` +shell> rvio foo.#.exr -leader simpleslate \ + "Acme Post" "Show=My Show" "Shot=foo" "Type=comp" \ + "Artist=John Doe" "Comments=Lighter and Darker as \ + requested by director" -o foo.mov +``` + +##### Watermark Overlay + +The watermark overlay burns a text comment onto output images. This makes it easy to generate custom watermarked movies for clients or vendors. Watermark takes two arguments, the comment in quotes and the opacity (from 0 to 1). + +``` +shell> rvio foo.mov -overlay watermark "For Client X Review" 0.1 \ + -o foo_client_x.mov +``` + +##### Matte Overlay + +The matte overlay mattes your images to the desired aspect ratio. It takes two arguments, the aspect ratio and the opacity. + +``` +shell> rvio foo.#.exr -overlay matte 2.35 1.0 -o foo.mov +``` + +##### Frameburn Overlay + +The frameburn overlay renders the source frame number onto each frame. It takes three arguments, the opacity, the luminance, and the size. + +``` +shell> rvio foo.#.exr -overlay frameburn 0.1 0.1 50 -o foo.mov +``` + +##### Bug Overlay + +The bug overlay lets you render an image o top of each output frame. It takes three arguments, the image name, the opacity, and the size + +``` +shell> rvio foo.#.exr -overlay bug "/path/to/logo.tif" 0.5 48 +``` + +#### 16.2.10 EXR Attributes + +RVIO can create and pass through header attributes. To create an attribute from the command line use -outparams: + +``` +shell> rvio in.exr -o foo.exr -outparams NAME:TYPE=VALUE0,VALUE1,VALUE2,... +``` + +TYPE is one of: + +| | | +| ---- | ------------------- | +| f | float | +| i | int | +| s | string | +| sv | Imf::StringVector | +| v2i | Imath::V2i | +| v2f | Imath::V2f | +| v3i | Imath::V3i | +| v3f | Imath::V3f | +| b2i | Imath::Box2i | +| b2f | Imath::Box2f | +| m33f | Imath::M33f | +| m44f | Imath::M44f | +| c | Imf::Chromaticities | + +Values are comma separated. For example to create a Imath::V2i attribute called myvec with the value V2i(1,2): + +``` +shell> rvio in.exr -o out.exr -outparams myvec:v2i=1,2 +``` + +similarily a string vector attribute would be: + +``` +shell> rvio in.exr -o out.exr -outparams mystringvector:sv=one,two,three,four +``` + +and a 3 by 3 float matrix attribute would be: + +``` +shell> rvio in.exr -o out.exr -outparams myfloatmatrix:m33f=1.0,2.0,3.0,4.0,5.0,6.0,7.0,8.0,9.0 +``` + +where the first row of the matrix would be 1.0 2.0 3.0. + +If you want to pass through attributes from the incoming image to the output EXR file you can use the passthrough variable. Setting passthrough to a regular expression will cause the writer code to select matching incoming attribute names. + +``` +shell> rvio in.exr -o out.exr -outparams "passthrough=.*" +``` + +The name matching includes any non-EXR format identifiers that are created by RV and RVIO. + +``` +shell> rvio in.jpg -o out.exr -insrgb -outparams "passthrough=.*EXIF.*" +``` + +The name matching includes any EXIF attributes (e.g. from TIFF or JPEG files). + +#### 16.2.11 IIF/ACES Files + +RVIO can convert pixels to the very wide gamut ACES color space for output using the -outaces flag. In addition, use of the .aces extension will cause the OpenEXR writer to enforce the IIF container subset of EXR. For example, to convert an existing EXR file to an IIF file: + +``` +shell> rvio in.exr -o out.aces -outaces +``` + +It's possible to write to other formats using -outaces, but it's not recommended. + +#### 16.2.12 DPX Header Fields + +Using -outparams it's possible to set almost any DPX header field. Setting the field will not change the pixels in the final file, just the value of the header field. + +There are a couple of fields treated in a special way: the frame_position, tv/time_code, and tv/user_bits fields are all increment automatically when a sequence of frames is output. In the case of tv/user_bits and tv/time_code, the initial value comes either from the output frame number, or starting at the time code passed in. + +*Table 16.2: DPX Output Parameters* + +| Keyword | Description | +| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| transfer | Transfer function (LOG, DENSITY, REC709, USER, VIDEO, SMPTE274M, REC601-625, REC601-525, NTSC, PAL, or number) | +| colorimetric | Colorimetric specification (REC709, USER, VIDEO, SMPTE274M, REC601-625, REC601-525, NTSC, PAL, or number) | +| creator | ASCII string | +| copyright | ASCII string | +| project | ASCII string | +| orientation | Pixel Origin string or int (TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, ROTATED_TOP_LEFT, ROTATED_TOP_RIGHT, ROTATED_BOTTOM_LEFT, ROTATED_BOTTOM_RIGHT) | +| create_time | ISO 8601 ASCII string: YYYY:MM:DD:hh:mm:ssTZ | +| film/mfg_id | 2 digit manufacturer ID edge code | +| film/type | 2 digit film type edge code | +| film/offset | 2 digit film offset in perfs edge code | +| film/prefix | 6 digit film prefix edge code | +| film/count | 4 digit film count edge code | +| film/format | 32 char film format (e.g. Academy) | +| film/frame_position | Frame position in sequence (0 indexed) | +| film/sequence_len | Sequence length | +| film/frame_rate | Frame rate (frames per second) | +| film/shutter_angle | Shutter angle in degrees | +| film/frame_id | 32 character frame identification | +| film/slate_info | 100 character slate info | +| tv/time_code | SMPTE time code as an ASCII string (e.g. 01:02:03:04) | +| tv/user_bits | SMPTE user bits as an ASCII string (e.g. 01:02:03:04) | +| tv/interlace | Interlace (0=no, 1=2:1) | +| tv/field_num | Field number | +| tv/video_signal | Video signal standard 0-254 (see DPX spec) | +| tv/horizontal_sample_rate | Horizontal sampling rate in Hz | +| tv/vertical_sample_rate | Vertical sampling rate in Hz | +| tv/frame_rate | Temporal sampling rate or frame rate in Hz | +| tv/time_offset | Time offset from sync to first pixel in ms | +| tv/gamma | Gamma | +| tv/black_level | Black level | +| tv/black_gain | Black gain | +| tv/break_point | Breakpoint | +| tv/white_level | White level | +| tv/integration_times | Integration times | +| source/x_offset | X offset | +| source/y_offset | X offset | +| source/x_center | X center | +| source/y_center | Y center | +| source/x_original_size | X original size | +| source/y_original_size | Y original size | +| source/file_name | Source file name | +| source/creation_time | Source creation time YYYY:MM:DD:hh:mm:ssTZ | +| source/input_dev | Input device name | +| source/input_dev | Input device serial number | +| source/border_XL | Border validity left | +| source/border_XR | Border validity right | +| source/border_YT | Border validity top | +| source/border_YB | Border validity bottom | +| source/pixel_aspect_H | Pixel aspect ratio horizontal component | +| source/pixel_aspect_V | Pixel aspect ratio vertical component | + + +This example set the start time code of the DPX sequence: + +``` +shell> rvio in.#.tif -o out.#.dpx -outparams tv/time_code=00:11:22:00 +``` + +It is also possible to set the alignment of pixel data relative to the start of the file using alignment. For example, to force the pixel data to start at byte 4096 in the DPX file: + +``` +shell> rvio in.#.tif -o out.#.dpx -outparams alignment=4096 +``` + +The smallest value for alignment is 2048 which includes the size of the default DPX headers. + +The DPX writer cannot automatically pass through header fields from input DPX images to the output DPX images. + +To set the project header value: + +``` +rvio in.#.exr -o out.#.dpx -outparams "project=THE PROJECT" +``` + +To set colorspace header values: + +``` +rvio in.#.exr -o out.#.dpx -outparams transfer=LINEAR colorimetric=REC709 +``` diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-ten.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-ten.md new file mode 100644 index 000000000..1233d64b9 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-ten.md @@ -0,0 +1,72 @@ +# Chapter 10 - Packages + +There are multiple ways to customize and extend RV, and almost all these ways can be encapsulated in an RV Package. A package is a zip file with source or binary code that extends RV's feature set. Packages are constructed so that they can be automatically installed and removed. This makes it much easier for you to maintain RV than if you modify RV source directly. (Note: although packages are zip files, they are labeled with a .rvpkg extension to prevent mail programs, etc, from automatically unzipping them.) + +Packages can encapsulate a wide variety of features: everything from setting the titile of the window automatically to implementing a paint annotation package. Some of RV's internal code is also in package form prior to shipping: e.g. the entire remote sync feature is initially constructed as a package of Mu source code (but is permanently installed when shipped). + +The package manager can be found in the preferences dialog. There are two sections to the user interface: the list of packages available and the description of the selected package. Some packages may be “hidden” by default; to see those packages toggle the show hidden packages button. To add and remove packages use the add and remove buttons located at the bottom left. + +The reference manual contains detailed information about how to create a package. + +### 10.1 Package Support Path + + +When a package is added, a list of permissible directories in the support path is presented. At that time you can choose which support directory to add the package to. When RV starts, these directories are automatically added to various paths (like image I/O plugins). + +By default, RV will include the application directory's plug-ins directory (which is probably not writable by the user) and one of the following which is usually writable by the user: + +| | | +| -------------------------------- | -------- | +| ~/Library/Application Support/RV | macOS | +| ~/.rv | Linux | +| $APPDATA/RV | Windows | + +You can override the default support path locations by setting the environment variable RV_SUPPORT_PATH. The variable contents should be the usual colon (Linux and Mac) or semicolon (Windows) separated list of directories. The user support directory is not included by default if you set the RV_SUPPORT_PATH variable, so be sure to explicitly include it if you want RV to include that path. Also note that the support path elements will have subdirectories called Mu, Packages, etc. In particular, when using the support path to install Packages, you want to include the directory above Packages in the path, not the Packages subdir itself. + +The file system of each directory in the support path contains these directories: + +| | | +| ------------- | ----------------------------------------------------------------------- | +| Packages/ | Package zip files | +| ConfigFiles/ | Area used by packages to store non-preference configuration information | +| ImageFormats/ | Image format Plug-Ins | +| MovieFormats/ | Movie format Plug-Ins | +| Mu/ | Mu files implementing packages | +| Python/ | Python files implementing packages | +| SupportFiles/ | Additional files used by packages (icons, etc) | +| lib/ | Shared libraries required by packages | + +RV will create this structure if it's not already there the first time you add or install a package. + +### 10.2 Installation + +To add a package: + +1. Open the Package Manager. +1. In the Packages tab, click **Add Packages...** +1. Navigate to the package's .rvpkg file. + +![51_ase_packagesShot.jpg](../../images/rv-user-manual-51-rv-cx-ase-packagesShot-50.jpg) + +Figure 10.1: Package Manager + +Tip: When you are troubleshooting packages you have installed, enable the **Show Hidden Packages** checkbox. + +Once a package has been added, to install or uninstall simply click on the check box next to the name. The package is installed in the same support directory in which it was added. + +A package can be added, removed, installed, and uninstalled for all users or by a single user. Usually administrator privileges are necessary to operate on packages system wide. When a package is added (the rvpkg file) it is copied into a known location in the support path. + +It's best to avoid editing files in these locations because RV tries to manage them itself. When a package is installed the contents will be installed in directories of the support directory. + +When first installed, packages are loaded by default. To prevent a package from loading uncheck the load check box. This is useful for installed packages which are not uninstallable because of permissions. While the install status of a package is universal to everyone that can see it, the load status is per-user. + +**Note** : A restart of RV is before a change in a package Installed or Loaded state takes effect. + +### 10.3 Package Dependencies + + +Packages may be dependent on other packages. If you select a package to be installed but it requires that other packages be installed as well, RV will ask you if it can install them immediately. A similar situation can occur when setting the load flag for a package. When uninstalling/unloading the opposite can happen: a package may be required by another that is “using” it. In that case RV will ask to uninstall/unload the dependent packages as well. + +Some packages may require a minimum version of RV. If a package requires a newer version, RV shouldn't allow you to install it. + +In some cases, manual editing of the support directory may lead to a partially installed or uninstalled package. The package manager has a limited ability to recover from that situation and will ask for guidance. \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-thirteen.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-thirteen.md new file mode 100644 index 000000000..4a229a87f --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-thirteen.md @@ -0,0 +1,124 @@ +# Chapter 13 - Networking + +RV implements a simple chat-like network protocol over TCP/IP. Network connections can be between two RVs or between a custom program and RV. The protocol is documented in the reference manual. This chapter discusses how to make network connections, how network permissions work, and how to synchronize two RVs using networking. + +Note that just turning on networking and establishing a connection doesn't actually do anything in RV. But this step is necessary before any of the features that use the networking can start. + +Some of the things you can do with networking include synchronizing to RV sessions across a network, sending pixels from another program to RV (e.g., a RenderMan display driver), or controlling RV from another program (remote control). Sync is part of RV, but remote control and sending pixels must be implemented in another program. RV comes with source code to a program called rvshell which shows you how to write software that connects with RV and controls playback remotely as well as sending images to RV over the connection. Some other applications are using special devices to control RV (like apple remote control for example) or controlling RV as an embedded playback component in a custom application on Linux. + +The reference manual has documentation about the networking protocol and how to create programs that talk to RV. The inner workings of the rvshell program are also discussed there. + +While it's possible to create connections across the internet, it's a little tricky, see [13.1.4](#1314-networking-across-the-internet) . + +**WARNING** : Care should be taken with RV networking. Once a connection is established to a client application (RV or any other client) all scripting commands (including "system(...)") are allowed and executed with the user id and permissions of the local RV user. Local connections (from a client application running on the same machine as RV), are not authenticated and the user is not required to confirm connection. Remote connections must be explicitly allowed by the user, but the identity of the remote user can not be confirmed. Also note that traffic over RV network connections is generally not encrypted. + +Use of RV Remote Sync is at your own risk. Although we may recommend preferred configurations, we cannot guarantee that any configuration will be secure or that your use of RV Remote Sync will not introduce vulnerabilities into your systems. If you do not wish to accept this risk, please do not use RV Remote Sync. + +### 13.1 The Network Dialog + + +Before you can use any of RV's networking features, you have to tell RV to begin listening for connections. There are two ways to do this: from the command line using the -network flag or interactively using the the Network dialog via the RV → Networking... menu item. + +The network dialog box has three pages: Configuration for setting up your identity and the port on which RV will communicate, the Contacts page for managing permissions, and the Connections page which shows a list of the currently active connections. At the bottom of the dialog are buttons for starting and stopping the networking and for initiating a new connection. + +![60_se_rv_networking.jpg](../../images/rv-user-manual-60-rv-cx-se-rv-networking-60.jpg) + +Figure 13.1: + +RV Network Dialog + +#### 13.1.1 Configuration + +The configuration requires two pieces of information: your identity (which appears at the other end of the connection) and a port number. Unless your systems administrator requires RV to use a different port you should leave the default value for the port number. Networking must be stopped in order to change the configuration. + +#### 13.1.2 Starting a Connection + +To start networking and have RV accept connection just hit the Start Network button. Once running, the status indicator in the bottom left-hand corner of the dialog will show you the network status and the number of active connections (which starts out as 0). + +There are two ways to initiate a new connection: by pressing the Connect... button at the bottom of the dialog or by selecting a known address on the Contacts page. + +Using the Connect... button will show another dialog asking for the host name of the machine to connect to. Using this method, RV will not care which user it finds at the other end of the connection. If RV does not yet have record of the user it finds it will create one. + +![61_network_connect.jpg](../../images/rv-user-manual-61-rv-cx-network-connect-61.jpg) + +Figure 13.2: + +Network Dialog Starting a Connection + +#### 13.1.3 Contacts and Permissions + +If networking is on, and a connection is initiated by another party, RV will ask whether or not you want the connection to occur. At that point you have three choices: accept the connection but ask for permission next time, always accept connection from the user/program that's asking, or deny the connection. + +Each new contact that RV receives (whether or not you accept) is recorded in the Contacts page. On this page you delete existing contacts, change permissions for contacts, and initiate connections to contacts. You can also specify the behavior RV when new contacts try to connect to your RV. This is most useful when RV is used by multiple people (for example in a common space like a view station). Often instances of RV running in common spaces should be more conservative about allowing connections without asking. + +To change an existing contacts permissions, double click on the current value (where it says, Allow, Ask, or Reject). You should see a pop-up menu which lets you change the value. + +To initiate a connection to an existing contact, double click on the contact name or machine name. + +There is also a pop-up menu which lets you delete an existing contact or initiate a connection. + +![62_network_contacts.jpg](../../images/rv-user-manual-62-rv-cx-network-contacts-62.jpg) + +Figure 13.3: + +Network Contacts Page + +#### 13.1.4 Networking Across the Internet + +It's possible to connect to a remote RV across the internet in a peer-to-peer fashion, but special care needs to be taken. We recommend one of two methods: using ssh tunneling or using a VPN. + +SSH tunnelling is well known in the IT community. Since there is no standard firewall configuration, you will need to understand both the way in which ssh tunnelling is set up and the topology of the firewalls on both ends of the connection. No third party connection is needed to sync across ssh tunnels and the connection is relatively secure. + +Using a VPN is no different than a local area network once it has been established: you need to know the IP address or name of the host with which you want to connect. If you do not have a VPN in common with the remote participant, you can create one using [Hamachi](https://secure.logmein.com/products/hamachi/vpn.asp) . The service is free for non-commercial use. See their website for more details. + +### 13.2 Synchronizing Multiple RVs + + +Once a connection has been established between two RVs, you can synchronize them. + +To start sync select the menu item Tools → Sync With Connected RVs. You should see \`\`Sync ON'' in the feedback area on both RVs and a Sync menu will appear in the menu bar. + +Usually it's a good idea to have all participants looking at the similar media, but it's not enforced. In particular, note that RV's auto-conforming features mean that one party can be looking at a high-res OpenEXR sequence and another at a qucktime movie of the same sequence, and the sync can still be quite useful. + +![63_rv_rv-cxx98-release_grabSync.png](../../images/rv-user-manual-63-rv-cxx98-release-grabSync-63.png) + +Figure 13.4: + +Sync Mode Showing Remote User's Cursor + +#### 13.2.1 Using Sync + +You can control which aspects of RV are transmitted to and received from remote RV's from the Sync menu. The menu is divided into to two main sections: things you can send and things you have agreed to receive. By default, sync mode will accept anything it gets, but will only send certain types of operations. So if one of the participants decides to send more than the default state the remote RVs will automatically use it. + +Sync mode will always send frame changes and playback options like the current fps, and the playback mode. + +![64_ase_grabSyncMenu.png](../../images/rv-user-manual-64-rv-cx-ase-grabSyncMenu-64.png) + +Figure 13.5: + +Sync Mode Menu + +| Event Group | What Gets Sync'ed | +| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Color | Any per-source color changes. E.g., exposure, gamma, hue, saturation and contrast. The File LUT is currently not synced between RVs. | +| Pan and Zoom | Any viewing pan and zoom changes. The scales are relative to the window geometry (which is not synced). | +| Stereo Settings | Settings that are per-source (e.g., those that are found under the Image → Stereo menu). Stereo settings from the under View → Stereo menu are not included. | +| Display Stereo Settings | Stereo settings that affect the entire session (e.g. those found under the View → Stereo menu). This includes the stereo display mode. Be careful when using this when any participant is not capable of hardware stereo viewing. | +| Display Color Settings | Overall brightness, red, green, blue, channel isolation, display gamma, display sRGB and Rec709 modes. Most of the items found under the View menu. The look and display LUTs are currently not synced. | +| Image Format Settings | Image resolution, color resolution, rotation, alpha type, pixel aspect ratio changes. | +| Audio Settings | Soundtrack and per-source audio volume, balance, cross-over, etc. Things found under the Audio menu. | +| Default | Frame changes, FPS, realtime and play all frames playback mode settings, in and out points. These are always sync'ed. | + +Table 13.1: + +Sync Mode Sending and Receiving Events + +### 13.3 “Streaming” Movie Media from Online Sources + +Media “paths” provided to RV may be URLs that link to online media. These may be provided via the command line, in RVSession Files, or through scripting. This functionality should be considered “Beta” and there are several caveats you should be aware of: + +* At the moment “https” (SSL) URLs are only supported on OSX. On Linux/Windows URLs must be “http”. +* The “media from online” workflow is a worst-case for RV's caching system, which generally assumes that the media can be accessed at arbitrary locations. In general, it's probably best to use Region Caching, with a large cache size, and allow the media to cache before playback begins. For media that is low-bandwidth, or for particularly low-latency connections, it may work well to use a large Lookahead Cache. +* The URL needs to end in “.mov” or “.mp4” or some other extension that the MIO_FFMPEG plugin will recognize. +* Audio may be a problem, since it is generally decoded and cached by a separate thread in RV. It may be best to turn on the “Cache All Audio” preference. +* The movie should be created with the "faststart" property (for RVIO this means adding the "-outparams of:movflags=faststart" command-line options). \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-three.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-three.md new file mode 100644 index 000000000..448b1f5af --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-three.md @@ -0,0 +1,414 @@ +# Chapter 3 - Command Line Usage + +In this chapter, the emphasis will be on using the software from a Unix-like shell. On Windows, this can be done via Cygwin's bash shell or tcsh. If you choose to use the command.com shell, the command syntax will be the same, but some features (like pattern matching, etc) which are common with Unix shells may not work. + +To use RV from a shell, you will need to have the binary executable in your path. On Linux and Windows the RV executable is located in the bin directory of the install tree. + +On macOS this can be done by including /Applications/RV64.app/Contents/MacOS in your path (assuming you installed RV there). The executable on macOS is called RV64; you can either type the name in as is or create an alias or symbolic link from rv to RV. + +There are a number of ways to start RV from the command line: + +``` +rv options +``` + +``` +rv options source1 source2 source3 ... +``` + +``` +rv options [ source1 source2 ... ] [ source4 source5 ... ] +``` + +``` +rv options [ source-options source1 source2 ... ] [ source-options source4 source5 ... ] +``` + +``` +rv file.rv +``` + +The command line options are not required and may appear throughout the command line. Sources are individual images, QuickTime .mov files, .avi files, audio files, image sequences or directory names. When specifying a .rv file, no other sources should be on the command line. (See Table [cap:RV-Command-Line](#per-source-command-line-options) ) + +The third example above uses square brackets around groups of sources. When sources appear between brackets they are called layers. These will be discussed in more detail below. + +If RV is started with no arguments, it will launch a blank window; you can later add source material to the window via file browser or drag and drop. + +Options are all preceded by a dash (minus sign) in the Unix tradition, even on windows. Some of them take arguments and some of them are flags which toggle the associated feature on or off. For example: + +``` +shell> rv -fullscreen foo.mov +``` + +plays back a movie file in full screen mode. In this case -fullscreen is a toggle which takes no arguments and foo.mov is one of the source material. If an option takes arguments, you supply them directly after the option: + +``` +shell> rv -fps 23.97 bar.#.exr +``` + +Here the -fps option (frames per second) requires a single floating point number + +1 + +A floating point number in this context means a number which may or may not have a decimal point. E.g., 10 and 10.5 are both floating point numbers. + +. With rare exception, RV's options are either toggles or take a single argument.gti + +The most important option to remember is -help. The help option causes RV to print out all of the options and command line syntax and can be anywhere in a command line. When unsure of what the next argument is or whether you can add more options to a long command line, you can always add -help onto the end of your command and immediately hit enter. At that point, RV will ignore the entire command and print the help out. You can always use your shell's history to get the command back, remove the -help option, and continue typing the rest of the command. + +``` +shell> rv -fps 30 -fullscreen -l -lram .5 -help +(rv shows help) +Usage: RV movie and image viewer +... +(hit up arrow in shell, back up over -help and continue typing) +shell> rv -fps 30 -fullscreen -l -lram .5 -play foo.mov +``` + +Finally, you can do some simple arithmetic on option arguments. For example if you know you want to apply an inverse gamma of 2.2 to an image to view it you could do this: + +``` +shell> rv -gamma 1/2.2 +``` + +which is identical to this: + +``` +shell> rv -gamma 0.454545454545 +``` + +### Troubleshooting Open RV + +Launching RV from the command line is the best way to troubleshoot RV by giving you access to error messages or crash dumps. + +### Command-Line Options + +| | | +| --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| -c | Use region frame cache | +| -l | Use look-ahead cache | +| -nc | Use no caching | +| -s float | Image scale reduction | +| -stereo *string* | Stereo mode (hardware, checker, scanline, anaglyph, left, right, pair, mirror, hsqueezed, vsqueezed) | +| -vsync int | Video Sync (1 = on, 0 = off, default = 0) | +| -comp *string* | Composite mode (over, add, difference, replace, default=replace) | +| -layout *string* | Layout mode (packed, row, column, manual) | +| -over | Same as -comp over -view defaultStack | +| -diff | Same as -comp difference -view defaultStack | +| -tile | Same as -comp tile -view defaultStack | +| -wipe | Same as -over with wipes enabled | +| -view *string* | Start with a particular view | +| -noSequence | Don't contract files into sequences | +| -inferSequence | Infer sequences from one file | +| -autoRetime *int* | Automatically retime conflicting media fps in sequences and stacks (1 = on, 0 = off, default = 1) | +| -rthreads int | Number of reader threads (default = 1) | +| -renderer *string* | Default renderer type (Composite or Direct) | +| -fullscreen | Start in fullscreen mode | +| -present | Start in presentation mode (using presentation device) | +| -presentAudio int | Use presentation audio device in presentation mode (1 = on, 0 = off) | +| -presentDevice string | Presentation mode device | +| -presentVideoFormat string | Presentation mode override video format (device specific) | +| -presentDataFormat string | Presentation mode override data format (device specific) | +| -screen *int* | Start on screen (0, 1, 2, ...) | +| -noBorders | No window manager decorations | +| -geometry *int int *[_ int int _] | Start geometry x, y, w, h | +| -init *string* | Override init script | +| -nofloat | Turn off floating point by default | +| -maxbits *int* | Maximum default bit depth (default=32) | +| -gamma *float* | Set display gamma (default=1) | +| -sRGB | Display using linear -> sRGB conversion | +| -rec709 | Display using linear -> Rec 709 conversion | +| -floatLUT *int* | Use floating point LUTs (requires hardware support, 1=yes, 0=no, default=_platform-dependant_) | +| -dlut *string* | Apply display LUT | +| -brightness *float* | Set display relative brightness in stops (default=0) | +| -resampleMethod *string* | Resampling method (area, linear, cube, nearest, default=area) | +| -eval *string* | Evaluate expression at every session start | +| -nomb | Hide menu bar on start up | +| -play | Play on startup | +| -fps float | Overall FPS | +| -cli | Mu command line interface | +| -vram *float* | VRAM usage limit in Mb, default = 64.000000 | +| -cram *float* | Max region cache RAM usage in Gb | +| -lram *float* | Max look-ahead cache RAM usage in Gb | +| -noPBO | Prevent use of GL PBOs for pixel transfer | +| -prefetch | Prefetch images for rendering | +| -bwait *float* | Max buffer wait time in cached seconds, default 5.0 | +| -lookback float | Percentage of the lookahead cache reserved for frames behind the playhead, default 25 | +| -yuv | Assume YUV hardware conversion | +| -volume float | Overall audio volume | +| -noaudio | Turn off audio | +| -audiofs *int* | Use fixed audio frame size (results are hardware dependant ... try 512) | +| -audioCachePacket *int* | Audio cache packet size in samples (default=512) | +| -audioMinCache *float* | Audio cache min size in seconds (default=0.300000) | +| -audioMaxCache *float* | Audio cache max size in seconds (default=0.600000) | +| -audioModule string | Use specific audio module | +| -audioDevice *int* | Use specific audio device (default=-1) | +| -audioRate float | Use specific output audio rate (default=ask hardware) | +| -audioPrecision int | Use specific output audio precision (default=16) | +| -audioNice *int* | Close audio device when not playing (may cause problems on some hardware) default=0 | +| -audioNoLock int | Do not use hardware audio/video synchronization (use software instead default=0) | +| -audioGlobalOffset int | Global audio offset in seconds | +| -bg string | Background pattern (default=black, grey18, grey50, checker, crosshatch) | +| -formats | Show all supported image and movie formats | +| -cmsTypes | Show all available Color Management Systems | +| -debug *string* | Debug category | +| -cinalt | Use alternate Cineon/DPX readers | +| -exrcpus *int* | EXR thread count (default=2) | +| -exrRGBA | EXR use basic RGBA interface (default=false) | +| -exrInherit | EXR guesses channel inheritance (default=false) | +| -exrIOMethod int [int] | EXR I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=0) and optional chunk size (default=61440) | +| -jpegRGBA | Make JPEG four channel RGBA on read (default=no, use RGB or YUV) | +| -jpegIOMethod int [int] | JPEG I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=0) and optional chunk size (default=61440) | +| -cinpixel *string* | Cineon/DPX pixel storage (default=RGB8_PLANAR) | +| -cinchroma | Cineon pixel storage (default=RGB8_PLANAR) | +| -cinIOMethod int [int] | ineon I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=3) and optional chunk size (default=61440) | +| -dpxpixel string | DPX pixel storage (default=RGB8_PLANAR) | +| -dpxchroma | Use DPX chromaticity values (for default reader only) | +| -dpxIOMethod int [int] | DPX I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=3) and optional chunk size (default=61440) | +| -tgaIOMethod int [int] | TARGA I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=2) and optional chunk size (default=61440) | +| -tiffIOMethod int [int] | TIFF I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=2) and optional chunk size (default=61440) | +| -noPrefs | Ignore preferences | +| -resetPrefs | Reset preferences to default values | +| -qtcss *string* | Use QT style sheet for UI | +| -qtstyle *string* | Use QT style, default="" | +| -qtdesktop | QT desktop aware, default=1 (on) | +| -xl | Aggressively absorb screen space for large media | +| -mouse int | Force tablet/stylus events to be treated as a mouse events, default=0 (off) | +| -network | Start networking | +| -networkPort int | Port for networking | +| -networkHost *string* | Alternate host/address for incoming connections | +| -networkConnect *string *[int] | Start networking and connect to host at port | +| -networkPerm int | Default network connection permission (0=Ask, 1=Allow, 2=Deny, default=0) | +| -reuse int | Try to re-use the current session for incoming URLs (1 = reuse session, 0 = new session, default = 1; macOS only) | +| -nopackages | Don't load any packages at startup (for debugging) | +| -encodeURL | Encode the command line as an rvlink URL, print, and exit | +| -bakeURL | Fully bake the command line as an rvlink URL, print, and exit | +| -flags *string* | Arbitrary flags (flag, or 'name=value') for use in Mu code | +| -prefsPath *string* | Alternate path to preferences directory | +| -registerHandler | Register this executable as the default rvlink protocol handler (macOS only) | +| -scheduler *string* | Thread scheduling policy (may require root, Linux only) | +| -priorities int int | Set display and audio thread priorities (may require root, Linux only) | +| -version | Show RV version number | +| -pa float | Set the Pixel Aspec Ratio | +| -ro int | Shifts first and last frames in the source range (range offset) | +| -rs int | Sets first frame number to argument and offsets the last frame number | +| -fps float | FPS override | +| -ao float | Audio Offset. Shifts audio in seconds (audio offset) | +| -so float | Set the Stereo Eye Relative Offset | +| -volume float | Audio volume override (default = 1) | +| -fcdl filename | Associate a file CDL with the source | +| -lcdl filename | Associate a look CDL with the source | +| -flut filename | Associate a file LUT with the source | +| -llut filename | Associate a look LUT with the source | +| -pclut *filename* | Associate a pre-cache software LUT with the source | +| -cmap *channels* | Remap color channels for this source (channel names separated by commas) | +| -select *selectType selectName* | Restrict loaded channels to a single view/layer/channel.* selectType* must be one of view, layer, or channel.* selectName* is a comma-separated list of view name, layer name, channel name. | +| -crop x0 y0 x1 y1 | Crop image to box (all integer arguments) | +| -uncrop width height x y | Inset image into larger virtual image (all integer arguments) | +| -in int | Cut-in frame for this source in default EDL | +| -out int | Cut-out frame for this source in default EDL | +| -noMovieAudio | Turn off source movie's baked-in audio (aka “-nma”) | + +Table 3.1: Per-Source Command Line Options + +### 3.1 Image Sequence Notation + +RV has a special syntax to describe image sequences as source movies. Sequences are assumed to be files with a common base name followed by a frame number and an image type extension. For example, the filenames foo.1.tif and foo.0001.tif would be interpreted as frame 1 of the TIFF sequence foo. RV sorts images by frame numbers in numeric order. It sorts image base names in lexical order. What this means is that RV will sort images into sequences the way you expect it to. Padding tricks are unnecessary for RV to get the image order correct; image order will be interpreted correctly. + +``` +foo.0001.tif foo.0002.tif foo.0003.tif foo.0004.tif +foo.0005.tif foo.0006.tif foo.0007.tif foo.0008.tif +foo.0009.tif foo.0010.tif +``` + +To play this image sequence in RV from the command line, you could start RV like this: + +``` +shell> rv foo.\*.tif +``` + +and RV will automatically attempt to group the files into a logical movie. ( **Note** : this will only work on Linux or Mac OS, or some other Unix-like shell, like cygwin on Windows.) + +When you want to play a subset of frames or audio needs to be included, you can specify the sequence using the \`\`#'' or \`\`@'' notation (similar to Shake's) or the printf-like notation using \`\`%'' similar to Nuke. + +``` +rv foo.#.tif +rv foo.2-8#.tif +rv foo.2-8@@@@.tif +rv foo.%04d.tif +rv foo.%04d.tif 2-8 +rv foo.#.tif 2-8 +``` + +The first example above plays all frames in the foo sequence, the second line plays frames starting at frame 2 through frame 8. The third line uses the \`\`@'' notation which forces RV to assume 0 padded frame numbers–in this case, four \`\`@'' characters indicate a four character padding. + +The next two examples use the printf-like syntax accepted by nuke. In the first case, the entire frame range is specified with the assumption that the frame numbers will be padded with 0 up to four characters (this notation will also work with 6 or other amounts of padding). In the final two examples, the range is limited to frames 2 through 8, and the range is passed as a separate argument. + +Sometimes, you will encounter or create an image sequence which is purposefully missing frames. For example, you may be rendering something that is very expensive to render and only wish to look at every tenth frame. In this case, you can specify the increment using the \`\`x'' character like this: + +``` +rv foo.1-100x10#.tif +``` + +or alternately like this using the \`\`@'' notation for padding to four digits: + +``` +rv foo.1-100x10@@@@.tif +``` + +or if the file was padded to three digits like foo.001.tif: + +``` +rv foo.1-100x10@@@.tif +``` + +In these examples, RV will play frames 1 through 100 by tens. So it will expect frames 1, 11, 21, 31, 41, on up to 91. + +If there is no obvious increment, but the frames need to be group into a sequence, you can list the frame numbers with commas: + +``` +rv foo.1,3,5,7,8,9#.tif +``` + +In many cases, RV can detect file types automatically even if a file extension is not present or is mis-labeled. + +**NOTE** : Use the same format for exporting multiple annotated frames. + +#### 3.1.1 Negative Frame Numbers + +RV can handle negative frames in image sequences. If the frame numbers are zero padded, they should look like so: + +``` +foo.-012.tif +foo.-001.tif +``` + +To specify in and out points on the command line in the presence of negative frames, just include the minus signs: + +``` +foo.-10-20#.tif +foo.-10--5#.tif +``` + +The first example uses frames -10 to +20. The second example uses frames -10 to -5. Although the use of the \`\`-'' character to specify ranges can make the sequence a bit visually confusing, the interpretation is not ambiguous. + +#### 3.1.2 Stereo Notation + +RV can accept stereo notation similar to Nuke's \`\`%v'' and \`\`%V'' syntax. By default, RV can only recognize left, right, Left, and Right for %V and for %v it will try L, R, or l and r. You can change the substitutions by setting the environment variables RV_STEREO_NAME_PAIRS and RV_STEREO_CHAR_PAIRS. These should be set to a colon separated list of values (even on windows). For example, the defaults would look like this: + +``` +RV_STEREO_NAME_PAIRS = left:right:Left:Right +RV_STEREO_CHAR_PAIRS = L:R:l:r +``` + +So for example, if you have two image sequences: + +``` +foo.0001.left.exr +foo.0002.left.exr +foo.0001.right.exr +foo.0002.right.exr +``` + +you could refer to the entire stereo sequence as: + +``` +foo.%04d.%V.exr +``` + +### 3.2 Source Layers from the Command Line + +You can create source material from multiple audio, image, and movie files from the command line by enclosing them in square brackets. The typical usage looks something like this: + +``` +shell> rv [ foo.#.exr soundtrack.aiff ] +``` + +Note that there are spaces around the brackets: they must be completely separated from subsequent arguments to be interpreted correctly. You cannot nest brackets and the brackets must be matched; for every begin bracket there must be an end bracket. + +#### 3.2.1 Associating Audio with Image Sequences or Movie Files + +Frequently a movie file or image sequence needs to be viewed with one or more separate audio files. When you have multiple layers on the command line and one or more of the layers are audio files, RV will play back the all of the audio files mixed together along with the images or movies. For example, to play back a two wav files with an image sequence: + +``` +shell> rv [ foo.#.exr first.wav second.wav ] +``` + +If you have a movie file which already has audio you can still add additional audio files to be played: + +``` +shell> rv [ movie_with_audio.mov more_audio.aiff ] +``` + +#### 3.2.2 Dual Image Sequences and/or Movie Files as Stereo + +It's not unusual to render left and right eyes separately and want to view them as stereo together. When you give RV multiple layers of movie files or image sequences, it uses the first two as the left and right eyes. + +``` +shell> rv [ left.#.exr right.#.exr ] +``` + +It's OK to mix and match formats with layers: + +``` +shell> rv [ left.mov right.100-300#.jpg ] +``` + +if you want audio too: + +``` +shell> rv [ left.#.exr right.#.exr soundtrack.aiff ] +``` + +As with the mono case, any number of audio files can be added: they will be played simultaneously. + +#### 3.2.3 Per-Source Arguments + +There are a few arguments which can be applied with in the square brackets (See Table [3.1](#per-source-command-line-options) ). The range start sets the first frame number to its argument; so for example to set the start frame of a movie file with or without a time code track so that it starts at frame 101: + +``` +shell> rv [ -rs 101 foo.mov ] +``` + +You must use the square brackets to set per-source arguments (and the square brackets must be surrounded by spaces). + +The -in and -out per-source arguments are an easy way to create an EDL on the command line, even when playing movie files. + +#### 3.2.4 A note on the -fps per-source argument + +The point of the -fps arg is to provide a scaling factor in cases where the frame rate of the media cannot be determined and you want to play an audio file with it. For example, if you want to play "[foo.#.dpx foo.wav]" in the same session with "[bar.#.dpx bar.wav]" but the "native frame rate of foo is 24fps and the native frame rate of bar is 30fps, then you might want to say: + +``` +shell> rv [foo.#.dpx foo.wav -fps 24 ] [ bar.#.dpx bar.wav -fps 30 ] +``` + +This will ensure that the video and audio are synced properly no matter what frame rate you use for playback. To clarify further, the per-source -fps flag has no relation to the frame rate that is used for playback, and in general RV plays media (all loaded media) at whatever single frame rate is currently in use. + +#### 3.2.5 Source Layer Caveats and Capabilities + +There are a number of things you should be aware of when using source layers. In most cases, RV will attempt to do something with what you give it. However, if your input is logically ambiguous the result may be different than what you expect. Here are some things you should avoid using in layers of a single source: + +- Images or movies with differing frame rates +- Images or movies with different image or pixel aspect ratios +- Images or movies which require special color correction for only one eye +- Images or movies stored with differing color spaces (e.g. cineon log images + jpeg) + +Here are some things that are OK to do with layers: + +- Images with different resolutions but the same image aspect ratio +- Images with different bit depths or number of channels or chroma sampling +- Audio files with different sample rates or bit depths +- Mixing movies with audio and separate audio files +- An image sequence for one eye and a movie for the other +- A movie with audio for one eye and a movie without audio for the other +- Audio files with no imagery + +### 3.3 Directories as Input + +If you give RV the name of a directory instead of a single file or an image sequence it will attempt to interpret the contents of the directory. RV will find any single images, image sequences, or single movie files that it can and present them as individual source movies. This is especially useful with the directory \`\`.'' on Linux and macOS. If you navigate in a shell to a directory that contains an image sequence for example, you need only type the following to play it: + +``` +rv . +``` + +You don't even need to get a directory listing. If RV finds multiple sequences or a sequence and movie files, it will sort and organize them into a playlist automatically. RV will attempt to read files without extensions if they look like image files (for example the file ends in a number). If RV is unable to parse the contents of a directory correctly, you will need to specify the image sequences directly to force it to read them. diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-twelve.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-twelve.md new file mode 100644 index 000000000..d937a6378 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-twelve.md @@ -0,0 +1,117 @@ +# Chapter 12 - Stereo Viewing + +RV can display the first two image layers as stereo. There are several options: + +* Anaglyph +* Side-by-Side +* Mirrored Side-by-Side +* DLP Checker +* Scanline Interleaved +* Left or Right eye only +* Hardware Left and Right Buffers + +The stereo rendering is designed to work with the other features of RV. In most cases, color corrections, image geometry manipulations, and display corrections will work in conjunctions with stereo viewing. + +When caching is on, all layers are cached (left and right eyes). You will need to reduce the image resolution to cache as many frames as non-stereo. + +The left and right eye images are normalized (i.e. conformed to fit the RV window) so they may have different resolutions and/or bit depths. However, it is not advisable to have differing aspect ratios. + +You can create stereo sources and set individual stereo parameters from the command line. See Section [3.2.2](rv-user-manual-chapter-three.md#322-dual-image-sequences-andor-movie-files-as-stereo) to see how to do this. + +### 12.1 Anaglyph or Luminance Anaglyph + + +The anaglyph modes display the left eye in the red channel and the right in eye in the green and blue channels (as cyan). If you were to wear colored red-cyan glasses and the eyes are correctly arranged, you should be able to see stereo with pseudo-color. Color anaglyph images work best for outdoor scenes (with lots of green) and in similar cases. They work very poorly with blue or green screen photography. For grey scale/non-color rendition of anaglyph, select the “Luminance Anaglyph” mode (Figure [12.4](#luminance-anaglyph-display) ).. + +For color anaglyphs, if the color contrast is too great, the stereo effect will be difficult to see. If you turn on luminance viewing (hit the "l" key in the user interface), you can improve the effect (Figure [12.2](#anaglyph-stereo-with-luminance-display) ). A similar solution is to desaturate the image slightly (hit shift "S" and scrub); this will reduce the color contrast but keep some hints of color (Figure [12.3](#anaglyph-display-with-desaturation) ). + +Compression artifacts can seriously degrade stereo viewing especially in anaglyph mode. QuickTime movies, for example, with low quality compression may look fine when viewed one eye at a time, but in anaglyph mode JPEG or similar artifacts will be greatly amplified by slight color differences. The best way to view compressed material is with luminance display turned on (no color). + +![52_se_dice_anaglyph.png](../../images/rv-user-manual-52-rv-cx-se-dice-anaglyph-51.png) + +Figure 12.1: Anaglyph Stereo Display + +![53_dice_luma_anaglyph.png](../../images/rv-user-manual-53-rv-cx-dice-luma-anaglyph-52.png) + +Figure 12.2: Anaglyph Stereo With Luminance Display + +![54_dice_desat_anaglyph.png](../../images/rv-user-manual-54-rv-cx-dice-desat-anaglyph-53.png) + +Figure 12.3: Anaglyph Display With Desaturation + +![54_dice_anaglyph.png](../../images/rv-user-manual-dice-lumanaglyph-54.png) + +Figure 12.4: Luminance Anaglyph Display + +### 12.2 Side-by-Side and Mirror + + +Side-by-Side mode displays the left and right eyes next to each other horizontally in full color. Some people are comfortable crossing their eyes to see stereo using this mode. + +Mirror mode is similar, but the right eye is flopped. If you need the left eye flopped, turn on mirror mode and select Image->Flop or hit shift-"X" this will have the effect of mirroring the left eye instead. Note that the same effect can be produced by flopping the right eye only in mirror mode. + +![55_dice_side_by_side.png](../../images/rv-user-manual-55-rv-cx-dice-side-by-side-55.png) + +Figure 12.5: + +Side-by-Side Stereo Display + +![56_ease_dice_mirror.png](../../images/rv-user-manual-56-rv-cx-ease-dice-mirror-56.png) + +Figure 12.6: + +Mirror Display Mode + +### 12.3 DLP Checker and Scanline + + +These modes are designed to work with DLP projectors or LCD displays that directly support stereo display. In particular RV supports the SpectronIQ HD LCD display and DLP projectors using the Texas Instrument's checkerboard 3D DLP input. + +![57_dice_dlp_and_scanline.png](../../images/rv-user-manual-57-rv-dice-dlp-and-scanline-57.png) + +Figure 12.7: + +DLP (left) and LCD Scanline (right) Stereo Display + +### 12.4 HDMI 1.4a + + +HDMI 1.4a stereo modes like Side-by-Side and Top-and-Bottom are supported via RV's Presentation Mode, described in Chapter [6](rv-user-manual-chapter-six.md#6-presentation-mode-and-video-devices) . To select one of these modes for your Presentation Device, you choose the appropriate Output Data Format in the Video preferences. + +### 12.5 Hardware Stereo Support + + +RV can render into left and right buffers if your graphics card supports hardware stereo. You can tell if this is the case by seeing if the menu item View → Stereo → Hardware can be selected. If so, RV should be able to create left and right buffers. There are a number of different ways to view stereo with a standard graphics card. See Chapter [B](rv-user-manual-chapter-b.md#b-stereo-setup) for information about how to setup each platform and what options are available at the hardware level. + +Typically, hardware support requires shutter glasses (monitor or projection) or polarized glasses (projection only) in order to be useful. + +### 12.6 Additional Stereo Operations + + +These options can be applied per-source as well as part of the global viewing stereo options. The per-source options can be found under the Image → Stereo menu and the global view options are under View → Stereo. + +#### 12.6.1 Swap eyes + +Swap eyes changes the order of the left and right eyes (left becomes right and vice versa). If you are looking at stereo pairs and you just cannot see the stereo, you may want to try swapping the eyes. It is difficult to view stereo when the eyes are inverted. + +#### 12.6.2 Relative Eye Offset + +Relative eye offset controls how separated the left and right images are horizontally. In the case of the anaglyph and hardware left and right buffer modes the offset value determines the fusion depth. Objects at the fusion depth appear coincident with the screen depth. In other words, they appear to be right on the screen: not behind it or in front of it. This is most evident in the anaglyph mode where the red and cyan components of a shape will not be separated if it's at the fusion depth. The units of the offset numbers are a percentage of the image width. + +You have a choice to either offset the eye images away from each other (both at the same time) or to offset the right eye only. + +![58_dice_offset_change.png](../../images/rv-user-manual-58-rv-cx-dice-offset-change-58.png) + +Figure 12.8: + +Changing stereo relative eye offset. The left is the original image viewed in anaglyph mode. The right has an additional offset applied. + +#### 12.6.3 Flip/Flop the Right Eye Only + +If you are projecting stereo and require one eye be flipped (vertical) or flopped (horizontal), you can select Image → Stereo → Flip Right Eye or Image → Stereo → Flop Right Eye. This can further be combined with Image → Flip and Image → Flop and rotation to move the images into the correct position. + +![59_dice_one_eye_flipped.png](../../images/rv-user-manual-59-rv-cx-dice-one-eye-flipped-59.png) + +Figure 12.9: + +Flipping One Eye \ No newline at end of file diff --git a/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-two.md b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-two.md new file mode 100644 index 000000000..1c6931645 --- /dev/null +++ b/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-two.md @@ -0,0 +1,3 @@ +# Chapter 2 - Installation + +Refer to the Open RV README to learn how to build and install Open RV. \ No newline at end of file diff --git a/doc/rv-packages.md b/doc/rv-packages.md new file mode 100644 index 000000000..17f276d7d --- /dev/null +++ b/doc/rv-packages.md @@ -0,0 +1,7 @@ +# Open RV Packages + +* [Align Start Frames Package (to slide sources in time so that they can be compared)](rv-packages/rv-align-start-frames.md) +* [Open RV/Maya Integration](rv-packages/rv-maya-integration.md) +* [Nuke style file syntax in Open RV](rv-packages/rv-nuke-file-syntax.md) +* [Nuke/Open RV integration](rv-packages/rv-nuke-integration.md) +* [OTIO Reader Package Overview](rv-packages/rv-otio-reader.md) diff --git a/doc/rv-packages/rv-align-start-frames.md b/doc/rv-packages/rv-align-start-frames.md new file mode 100644 index 000000000..741cecb84 --- /dev/null +++ b/doc/rv-packages/rv-align-start-frames.md @@ -0,0 +1,13 @@ +# Align Start Frames Package (to slide sources in time so that they can be compared) + +This is an unsupported example that will probably be replaced with more complete tools in RV. + +This package adds an "Align Start Frames" menu item to the the Edit menu.  This action offsets the start frame of all the loaded movies and sequences so that they match the start frame of the first source (the first loaded sequence or movie). + +As you may be aware, RV preserves the time information represented by frame numbers. This has the advantage of placing sequences properly in time according to frame numbers. So in RV if you have a version of a shot foo.12-120#.exr and another version with different handles or cut points, foo_v2.18-122#.exr adn you load these into RV to do a wipe compare, then the frames will line up correctly in time. However, if you load a movie file (a quicktime) then it will start at frame 1 regardless of the source frames it represents. This package will let you compare a movie with the frames it came from by sliding all sources in time to line up on the same frame. + +Similar results can also be accomplished on the command line using the range offset flag: + +``` + rv [ foo.mov -ro 12 ] bar.12-120#.exr +``` diff --git a/doc/rv-packages/rv-maya-integration.md b/doc/rv-packages/rv-maya-integration.md new file mode 100644 index 000000000..eb77970d3 --- /dev/null +++ b/doc/rv-packages/rv-maya-integration.md @@ -0,0 +1,124 @@ +# RV/Maya Integration (v1.4) + +### Requirements + +The Maya integration package has only been tested with Maya 2012 and Maya 2014. + +### Installation + +The first step on any platform is to activate the integration package. That is, go to the *Packages* tab of the RV Preferences dialog, and click the Load button next to "Maya Tools", then restart RV. + +#### Linux and Windows + +On Linux and Windows, the only remaining task is to fill in the Application and Flags to run RV. In Maya open the Preferences, and go to the *Applications* section (titled "External Applications: Settings"). In the entry marked *Application Path for Viewing Sequences* enter the complete path to the *RVPUSH* executable. For example: + +Linux + +``` + /usr/local/bin/rv/bin/rvpush +``` + +Windows + +``` + C:\Program Files (x86)\RV\bin\rvpush.exe +``` + +Then, in the entry marked *Optional Flags* , type this: + +``` + -tag playblast merge %f +``` + +#### OSX (Maya 2012 or Maya 2013) + +On OSX, using Maya 2012 or Maya 2013, after turning on the Maya integration as described above and restarting RV, you need to install a tiny MEL script to handle the Maya side of the playblasting. In RV, go to the Maya menu, and select *Install Maya Support File.* (The file is installed in `Library/Preferences/Autodesk/maya/scripts` .) + +Then, in Maya open the Preferences, and go to the *Applications* section (titled "External Applications: Settings"). In the entry marked *Application Path for Viewing Sequences* enter the Following: + +``` + playblastWithRV +``` + +Then, in the entry marked *Optional Flags* , type this: + +``` + %f %r +``` + +Done! + +#### OSX (Maya 2014) + +On OSX, using Maya 2014, after turning on the Maya integration as described above, and quitting RV, in Maya open the Preferences, go to the *Applications* section (titled "External Applications: Settings"). In the section marked *Sequence Viewing Applications* there are three applications, each with two entries, one for the application, and one for *Optional Flags* . In each application entry (assuming you’ve installed RV in the usual location), enter the following: + +``` + unset QT_MAC_NO_NATIVE_MENUBAR; /Applications/RV64.app/Contents/MacOS/rvpush +``` + +Then, in each entry marked *Optional Flags* , type this: + +``` + -tag playblast merge [ %f -fps %r ] +``` + +Done! + +### Build a Session in Open RV + +The main point to remember about playblasting to RV is that if you leave RV open, each successive playblast will be merged into the RV Session, so you can easily compare the current render with others in the session. + +Open the Session Manager (from the *Tools* menu, or by hitting the *x* key) to see a list your playblasts and view them in different ways. Each playblast will have a name provided by Maya, plus a timestamp that RV adds to help you track them. If you want to rename a playblast to something more meaningful, just select it in the Session Manager, and select the *Edit View Info* button from the right-click menu, or hit the button with the *I* on it. + +At any point, you can save your Session to a Session File, so that you can easily pick up where you left off. When you return, render one playblast to get RV going, them *File→Merge* to bring in the Session File from the previous session. + +### Organizing and Comparing your Renders + +By default, each new playblast that Maya adds to your session will become the current view. Note that you can easily switch back and forth between this Render and the previous one with the *shift-left/right-arrow* keys. + +The "default" views (see the Session Manager) also let you easily see all your playblasts in a Sequence, in a Layout, or arranged in a Stack (note that you can use the "()" keys to easily cycle the stack, or you can use drag-and-drop in the Inputs section of the Session Manger to easily rearrange a group view. + +If you’d like to "stick" to the view you’ve chosen, instead of switching to each new playblast as it arrives, toggle the *View Latest Playblast* option off on the *Maya* menu in RV. + +### Creating New Views + +All the usual options in the Session Manager for creating and managing new views are available for managing your playblasts, but there are also a couple of hand shortcuts on the *Maya* menu. + +Wipe Selected Playblasts + +Select two or more playblasts in the Session Manager and hit this item to arrange them in a Stack, with Wipes mode activated, so that you can grab the edges of the images and slide them over the lower images. Remember that the "()" keys will cycle the stack order, and you can also drag and drop in the Inputs section of the Session Manager to reorder the stack. + +Tile Selected Playblasts + +Select two or more playblasts in the Session Manager and hit this item to arrange them in a Tiled Layout. + +### Rendering into Context + +Don’t forget that you can also bring in supplementary media to compare or use as reference for your animation. So for example you might bring in takes of the shots on either side of the one you’re animating, and assemble a 3-shot sequence with one of your playblasts in the middle. But as you work, you’d like that middle shot to be replaced by newer playblasts. We call that "rendering into context". + +In general, to prepare to render into context, after you setup your views, you’d + +1. Turn off the *View Latest Playblast* option in the *Maya* menu. + +2. Select (in the Session Manager) the previously-rendered playblast you want to swap out for new ones + +3. Pick the *Mark Selected as Target* item on the *Maya* menu. + + +Henceforth, future playblasts will be swapped into the slot occupied by the one you just marked. Possible "render into context" workflows include: + +Your shot in the cut + +Load the shots before and after yours and make a Sequence view. Note that you can trim shots by adjusting the in/out points on the timeline in the Source view. + +Wipe between the latest playblast and a previous take, or reference footage + +Add one or more sources (or use a previous playblast). Make a Stack view. Order the Stack to your liking by dragging and dropping in the Inputs section of the Session Manager. Turn on Wipes (Tools menu). + +Tile newest playblast with one or more others, reference footage, etc + +Add one or more sources (or use a previous playblast). Make a Layout view. Select Tile/Column/Row (or even arrange by hand with the Manual mode) from the *Layout* menu. Arrange the Layout to your liking by dragging and dropping in the Inputs section of the Session Manager. + +* * * + +Last updated 2014-04-08 12:29:03 PDT \ No newline at end of file diff --git a/doc/rv-packages/rv-nuke-file-syntax.md b/doc/rv-packages/rv-nuke-file-syntax.md new file mode 100644 index 000000000..958143b20 --- /dev/null +++ b/doc/rv-packages/rv-nuke-file-syntax.md @@ -0,0 +1,38 @@ +# Nuke style file syntax in Open RV + +You can use nuke's printf-esque %d syntax in RV and we've extended the existing shake-esque syntax so you can mix and match. + +> **Note:** previous versions of RV required using -ns in order to use nuke-like syntax. This is no longer necessary. + +Given some files called foo.0001.exr, foo.0002.exr, foo.0003.exr, .... foo.0100.exr: + +From the shell you can refer to the entire sequence using any of the following: + +``` +shell> rv foo.#.exr +shell> rv foo.@@@@.exr +shell> rv foo.%04d.exr +``` + +If you're current directory contains the sequence this will also work (in a unix shell): + +``` +shell> rv foo.*.exr +``` + +To constrain the sequence to frames 1 through 50 any of the following will work: + +``` +shell> rv foo.1-50#.exr +shell> rv foo.%04d.exr 1-50 +shell> rv foo.1-50%04d.exr +shell> rv foo.#.exr 1-50  +``` + +Its also possible to use multiple ranges, but only the shake-like method will work for that currently: + +``` +shell> rv foo.1-10,20-50,60-100#.exr +``` + +Any of these should also work inside brackets [ ]. diff --git a/doc/rv-packages/rv-nuke-integration.md b/doc/rv-packages/rv-nuke-integration.md new file mode 100644 index 000000000..4dc38f51b --- /dev/null +++ b/doc/rv-packages/rv-nuke-integration.md @@ -0,0 +1,252 @@ +# Nuke/Open RV integration + +## Introduction + +Rather than just attach a “flipbook” to Nuke, the goal of this integration effort is to provide compositors with a unified framework in which RV’s core media functionality (playback, browsing, arranging, editing, etc) is always instantly available to augment and enhance Nuke’s own capabilities. + +Key features include: + +* Checkpointing: Save a rendered frame with a copy of the current nuke script + +* Rendering: Save a rendered sequence with a copy of the current nuke script + +* Background rendering in Nuke 6.2 **and** 6.1 + +* Live update of RV during renders, showing the latest frame rendered + +* Rendered frames visible in RV as soon as they are written + +* Rendered frames from canceled renders are visible + +* Render directly into a slap comp or sequence in RV + +* Full checkpoints: copies of entire ranges of frames, for comparison + +* Visual browsing of checkpoints and renders + +* Visual comparison (wipes, tiled) of checkpoints and renders + +* Restoring the script to the state of any checkpoint or render + +* Read and Write nodes in the script dynamically mirrored as sources in RV + +* Read/Write node path, frame range, color space dynamically synced to RV + +* Node selection dynamically synced to the View in RV + +* Frame changes in Nuke dynamically synced to RV frame + +* RV Sources can be used to create the corresponding Nuke Read node + +* All Render/Checkpoint context retained in session file on disk + +* Support for `%V` -style stereoscopic Reads, Writes, renders, and checkpoints. + + +## Note to Users + + +Thanks for trying out the software; the integration toolset is in active development, and we’d very much appreciate any bug reports, feature requests, or other comments. + +Before you send us bug reports and feature requests, however, you might want to check over the list of known issues and planned work in [this appendix](#appendix-known-issues-and-planned-work) . + + +## Updating an Existing Installation + +If this Package is updated within a new Open RV distribution, you might need to update the Python code on the “Nuke side”. To do so, just follow the Installation instructions below. + +If the RV and Nuke components of the integration package are mis-matched, you’ll get an error dialog when you start RV from Nuke. + +## Installation + + +### Personal Installation + +1. Start RV and go to the *Packages* tab of the *Preferences* dialog + +2. Find *Nuke Integration* in the Package list and click the *Load* toggle next to it. + +3. Restart RV + +4. Click the *Nuke* item on the *Tools* menu + +5. From the *Nuke* menu, select the *Install Nuke Support Files* item and follow the directions. + + +To confirm that the Nuke support files are properly installed, start Nuke. You should see an *RV* menu on the main menubar, and if you select *RV/Preferences…* , you should get the appropriate dialog. + +That’s it for installation! + +### Site-wide Installation + +In what follows we suppose that you’ve installed RV in `/usr/local/RV` , and that you keep your Nuke scripts in subdirectories of `/usr/local/nuke/scripts` . If you do otherwise please adjust the paths below appropriately. + +1. Make a subdir in your Nuke scripts area for the rvnuke support files: + + ``` + % mkdir /usr/local/nuke/scripts/rvnuke + ``` + +2. Copy the Nuke support files into place + + ``` + % cp /usr/local/rv/plugins/SupportFiles/rvnuke/* /usr/local/nuke/scripts/rvnuke + ``` + +3. Edit the `init.py` file in `/usr/local/nuke/scripts` to include this line: + + ``` + nuke.pluginAddPath('./rvnuke') + ``` + + +Done! + +## Getting Started + + +### Rv Preferences + +In order to launch RV from Nuke, Nuke needs to know where the RV executable is. To set this, start Nuke and select the *RV/Preferences…* menu item. Navigate to the RV executable you want to use with Nuke and hit *OK* . + +This setting is stored and used across all future Nuke sessions. + +You can also specify any additional default command line arguments for RV in the *RV Preferences* dialog. + +If you have a RAID or other fast storage device you may want to configure the RV/Nuke integration to use a directory on this device as the base for all Session directories (see below). If so set the "Default Session Dir Base" preference accordingly. + +### Rv Project Settings + +There are several settings that the integration uses that may be different for different Nuke projects. Once you have a script loaded, select the *RV/Project Settings…* menu item, and then the *RV* tab of the Project Settings. + +The table below lists all the RV Project Settings, with explanations, but the most important is the “Session Directory.” This directory is where all media, script versions, and other information is stored for this Nuke script/project. It **must** be unique for each project. + +Session Directory + +The root directory for all media, scripts and other information related to this project. It will be created if it does not exist. Since media will be stored under this directory, you may want to put it on a device with fast IO. This name **must** be uniqe across all projects. + +You can set "Default Session Dir Base" in the RV Preferences (see above) so that by default all Session directories are created on your fast IO device. + +Render File Format + +The format of all media files created by rendering and checkpointing. + +Nuke Node Selection → RV Current View + +If this box is checked, every time you select a node in nuke, if RV is connected, the current RV view node will be set to the corresponding view. This lets you quickly view or play media, either input media associated with a Read node, or rendered media associated with any node that has been checkpointed or rendered. + +Nuke Frame → RV Frame + +If this box is checked, frame changes in Nuke will force the corresponding frame change in RV. + +Nuke Read Node Changes → RV Sources + +If this box is checked, the total set of Read nodes in the project will be dynamically synced to RV. That is, for every Read node in the project, there will be a corresponding Source in RV with the same media, available for playback on demand. Adding or Deleteing a Read node in Nuke will trigger the corresponding action in RV. Changes to Read node file path, frame range, and color space will also be reflected in RV. + +### Quick Start Summary + +You must set the RV executable path using the *RV/Preferences..* menu item before you use RV with Nuke at all, and whenever you start work on a new project/script, use *RV/Project Settings…* to make sure that the *Session Directory* is set to something reasonable before you start RV from that script for the first time. See above for details. + +### Open RV Toolbar + +Note that all the items on the RV menu are also available on the RV toolbar, which you can find in the Panes submenu. + +## Read/Write Nodes + + +Once you’ve set the RV path and Session Dir as described above, and have an interesting Nuke script loaded, try starting up RV with the *RV/Start RV* menu item. Assuming you have the *Sync Read Changes* setting active, as soon as RV starts you should see all the Read nodes in the script reflected as media Sources in RV. + +If you don’t see the Session Manager, try hitting the *x* to bring it up. In the Session Manager, You’ll see a Folder called “Read Nodes” with a Source for each Read node in the script. Each source is labeled with the name of the corresponding Read node, and a timestamp for when it was last modified. + +> **Note:** The Session Manager behavior at RV start-up can be set to "aways shown", "always hidden" or "remember previous state" using the "wrench" menu on the Session Manager. + +You can double-click on each Source to play just that one, or on the “Read Nodes” folder to see them all. + +Back in Nuke, note that if you edit the Path, Frame Range, or Color Space attributes of a Read node, the changes are reflected in the corresponding Source in RV. + +If the *Sync Selection* setting is active, as you select various Read nodes in Nuke, the RV current view switches to the corresponding Source. + +Also, if the *Sync Frame* setting is active, frame changes in the Nuke viewer will be reflected in RV. + +Note that if you don’t want all Read Nodes to be synced automatically, you can still sync some (or all) of them when you want to with the appropriate items on the *RV* menu. + +Pretty much all the above applies to Write nodes as well. + +## Checkpoints and Renders + + +As with Read nodes, Checkpoints and Renders are representations in RV of particular nodes in Nuke. So the Frame and Selection syncing described in the Read Nodes section applies to Checkpoints and Renders as well. + +Unlike Read nodes, the media associated with Checkpoints and Renders are generated from the Nuke script and so reflect the state of the script at the time of rendering. + +### Checkpoints + +The point of a Checkpoint is to to visually label a particular point in your projects development, so that you can easily return to that point if you want to. When you’ve made some changes in your script, and reach a point where you want to go in another direction, or try something out, or work on a different aspect of the project, that’s a good time to “bookmark” your work with a Checkpoint. + +To make a Checkpoint, select a node that visually reflects the state of the script and select *RV/Create Checkpoint* . You’ll see a new Source appear in RV, in a Folder named for the node you selected, with a single rendered frame from that node. + +As you work on a particular aspect of your project, you may want to make many Checkpoints of a particular node, so that you can easily compare the visual effect of different parameter settings. They’ll all be collected in a single folder in the Session Manager, and as with Read nodes, you can double click on a single one to view it, or double click on the folder itself to see them all. + +### Rendering + +A Render is similar to a Checkpoint, but involves rendering a sequence of frames, instead of just one. To render, select the node of interest, then select *RV/Render to RV* . You’ll get a dialog with some parameters: + +Output Node + +The name of the node to be rendered. + +Use Selected + +If checked, the output node will always be equal to whatever node is selected when the dialog is shown. If unchecked, the output node will “stick” and not be affected by the selection. + +First Frame + +The first frame in the sequence to be rendered. + +Last Frame + +The last frame in the sequence to be rendered. + +Since Renders can occupy significant disk space, successive renders of the same node overwrite any pre-existing render. But each render also automatically generates a single-frame Checkpoint of the same Nuke state. Also, deleting a Render or Checkpoint in the Session Manager (with the Trash Can button), also removes the corresponding media from disk. + +During a Render, RV updates dynamically to show you all the frames rendered so far. If the render is canceled, you still see in RV any frames that completed before the cancel. RV Sources from renders go into the same Folder as Checkpoints from the same node. + +### Full Checkpoints + +A Full Checkpoint is just like a regular checkpoint except that an entire sequence of frames is saved. To create a Full Checkpoint, select a Render in the RV Session Manager and then select *Create Full Checkpoint* from the *Nuke* menu in RV. + +## Working with Media in Open RV + +Relevant here is the chapter on the [Session Manager](../rv-manuals/rv-user-manual/rv-user-manual-chapter-five.md) and the section on [navigation](../rv-manuals/rv-user-manual/rv-user-manual-chapter-five.md#54-navigating-between-views) + +### Folders + +The Nuke integration makes use of Folders to organize your media. You’ll have a folder for all your Read nodes, a folder of checkpoints and renders for each rendered node, and a catch-all folder called “Other” to collect the rest. All folders are viewable and make for a handy “browsing” interface. + +### Comparisons + +You can easily Compare two or more renders or checkpoints (or any views, actually). Just select the views of interest in the Session Managerand select on the comparison items on RV’s *Nuke* menu: *Nuke/Wipe Selected Views* or *Nuke/Tile Selected Views* . + +## Modifying the Nuke Project from Open RV + + +### Restoring Checkpoints + +Any Checkpoint (or Render) can provide a source from which the Nuke project can be restored to the state it was in when the Checkpoint’s media was rendered. To restore a Checkpoint, select it in the RV Session Manager, and choose *Nuke/Restore Checkpoint* . After a confirmation dialog, the Nuke script will be restored. + +The navigation techniques referenced above combine with checkpoint restoration to produce some nice workflows (I think). For example: + +1. After lots of rendering and checkpointing of node *FinalMerge* , double-click on the *Renders of FinalMege* folder to see a layout of all the checkpoints and renders. + +2. Bring up the Image Info widget to mouse around and see the names and timestamps of all the views in the layout. + +3. Double click on one if the tiles to examine that checkpoint more closely. + +4. Decide to restore this checkpoint, it’s alread selected, so just hit *Nuke/Restore Checkpoint* + + +Also note that the Restore operation is undo-able, from the Nuke *Edit* menu. + +### Adding Read Nodes + +Of course you can still view media that’s unconnected to the Nuke project in a connected RV. So you can for example browse an element library. Once you have media that you’d like to include in your project, just select the Sources in the Session Manager and choose *Nuke/Create Nuke Read Node* . The corresponding Read node will be created in Nuke. Actually you can create any number at once by just selecting however many you want. diff --git a/doc/rv-packages/rv-otio-reader.md b/doc/rv-packages/rv-otio-reader.md new file mode 100644 index 000000000..e755265ed --- /dev/null +++ b/doc/rv-packages/rv-otio-reader.md @@ -0,0 +1,225 @@ +# OTIO Reader Package Overview + +The RV package plugin is based on the [OTIO](http://opentimeline.io/) project. + +The OTIO Reader is an RV package that allows RV to import and export OTIO files. The package is installed and loaded by default with support for all OTIO schemas part of OTIO version 0.15. So users can now import and export OTIO files through any one of the various RV import/export paths. + +The rest of this section will detail how to customize the import and export processes, and how to support custom OTIO schemas or metadata. + +## Imported and exported nodes + +When importing, the resulting RV node graph will be added to the current view node. When exporting, the current view node will be the root of the exported OTIO. Since not all RV nodes are supported as the root of an OTIO file, the export option is only available when the view node is an RVSequenceGroup, RVStackGroup, or RVSourceGroup. + +## Package contents + +The RV package is installed in the usual Python package installation location: `/Plugins/Python`. + +The package also uses a number of files located in `Plugins/SupportFiles/otio_reader` and detailed below. + +- `manifest.json`: Provides examples of schemas and hooks used in the import process. +- `cdlExportHook.py`: An example of exporting an RVLinearize to a custom CDL effect in OTIO. +- `cdlHook.py`: An example of importing a custom CDL effect in OTIO into an RVLinearize node. +- `cdlSchema.py` An example schema of a CDL effect. +- `clipHook.py`: An example of a hook called before importing a clip. +- `customTransitionHook.py`: An example of importing a custom transition in OTIO into RV. +- `effectHook.py`: A helper file for adding and setting RV properties from OTIO. +- `sourcePostExportHook.py`: A hook called after an RVSourceNodeGroup has been exported to a `Clip`. This can be used to add custom effects for other nodes within the same source group. The RVLinearize node is provided as an example. +- `retimeExportHook.py`: A hook for exporting an RVRetime node to OTIO schemas LinearTimeWarp or FreezeFrame. +- `timeWarpHook.py`: A hook for importing OTIO's LinearTimeWarp and FreezeFrame schemas. + +## Using the OTIO hook system + +The RV package is using the existing OTIO hook system to execute its hooks, so any existing `manifest.json` in the `OTIO_PLUGIN_MANIFEST_PATH` environment variable can be modified to work with RV's hook system, including RV's own `manifest.json` file. + +The `OTIO_PLUGIN_MANIFEST_PATH` environment variable can be modified to work with RV's hook system, including RV's own manifest.json. + +There are three types of OTIO hooks that will be called by the RV package: + +- pre- and post-hooks for both import and export +- custom schema hooks for import +- custom transition hook for import +- custom RV node hooks for export + +### Pre and post-hooks + +The pre- and post-hooks are called before each known OTIO schema during import or RV node during export. For import, they are named `pre_hook_[schema_name]` and `post_hook_[schema_name]` and will be called just before and just after processing the schema. For export, they are similarly named `pre_export_hook_[node_name]` and `post_export_hook_[node_name]`. For example, a hook can be added named `pre_hook_Clip` and it will be called just before the import process has created an RV source based on the Clip. These hooks are useful for handling custom metadata in schemas. The provided `clipHook.py` is an example of this. + +### Custom schema hooks + +During import, the custom schema hooks are called whenever the `otio_reader` encounters a schema it cannot handle. These hooks are named `[schema_name]_to_rv`. For example, `CDL_to_rv` will be called whenever a schema named CDL is found. The provided `cdlHook.py` is an example of this. This is most commonly used for custom effects and other schemas that are not provided by OTIO. + +The custom transition hook is similar to the custom schema hooks, except instead of being based on the schema name, it is called whenever the `transition_type` field of the Transition schema is set to Custom. The `customTransitionHook.py` is an example of this. + +### Custom transition hook + +The custom transition hook is similar to the custom schema hooks, except instead of being based on the schema name, it is called whenever the `transition_type` field of the Transition schema is set to Custom. The `customTransitionHook.py` is an example of this. + +### Custom Open RV node hooks + +During export, the custom RV node hooks are called whenever the `otio_reader` encounters a node it cannot handle. These hooks are named `export_[node_name]`. The provided `cdlExportHook.py` is an example of this. This is commonly used for effects in RV that do not have equivalent OTIO schemas. + +### Import Hook file parameters + +The `in_timeline` parameter in the OTIO hook functions will be set to the OTIO schema being processed, not the full timeline. For example, in `cdlHook.py` the `in_timeline` is the CDL effect and in the `pre_hook_Clip` it is the Clip. + +The `argument_map` will contain the context that can be helpful when creating RV nodes. For example, the following keys can currently be present, depending on which hook is being called: + +- `transition`: `RV transition` output node +- `stack`: `RVStackGroup` output node +- `sequence`: RVSequenceGroup output node +- `source`: `RVSource` output node +- `source_group`: `RVSourceGroup` output node +- `track_kind`: `OTIO track_kind` property of the OTIO Track currently being processed + +The `in_timeline parameter` in the OTIO hook functions will be set to the OTIO schema being processed—not the full timeline. For example, in `cdlHook.py` the `in_timeline` is the CDL effect and in the `pre_hook_Clip` it is the Clip. + +The `argument_map` will contain the context that can be helpful when creating RV nodes. For example, the following keys can currently be present, depending on which hook is being called: + +- `transition`: `RV transition` output node +- `stack`: `RVStackGroup` output node +- `sequence`: `RVSequenceGroup` output node +- `source`: `RVSource` output node +- `source_group`: `RVSourceGroup` output node +- `track_kind`: `OTIO track_kind` property of the OTIO Track currently being processed +- `global_start_time`: The `global_start_time` of the OTIO Timeline. + +### Import Hook file return values + +When RV nodes are being created—such as effects or transitions—the return value should be set to the name of the created node. The `otio_reader` will add the node as input at the current location in RV's node graph. It will also have its metadata added as a new property on the node named `otio.metadata`. + +For pre- and post- hooks, no return value is expected. However, the pre-hooks can optionally return False, which will quit processing the current schema and all its children. + +### Export Hook file parameters + +The `in_timeline` parameter in the OTIO hook functions will be set to the OTIO timeline that has been constructed up to that point. The `argument_map` will contain an `rv_node_name` key with its value set to the name of the RV node being processed. + +Depending on the context in which the hook is called, the `argument_map` parameter can make the following keys available: + +- `in_frame`: The `edl.in` value for clips and gaps +- `out_frame`: The `edl.out` value for clips and gaps +- `cut_in_frame`: The `edl.frame` value for clips and gaps +- `pre_item`: The OTIO item before a transition +- `post_item`: The OTIO item after a transition + + +### Export Hook file return values + +If an effect returned, the plugin will add it to the effect list of the OTIO Clip or Gap. The OTIO Clip or Gap is created from the RVSourceGroup to which the effect is attached. + +For pre- and post- hooks, no return value is expected. However, the pre-hook nodes can optionally return `False`, which will quit processing the current node and all of its inputs. + + +## Media Multi-Reference Support + +If you're using OTIO version 0.15 or later, you have access to the Media Multi-Reference (MMR) feature. + +With MMR, known and supported in RV as [Multiple Media Representations](../rv-manuals/rv-media-multi-representation.md), a clip can reference high-resolution media and proxies. In RV, this allows the user to easily switch between the different representations of the same media. + +To accommodate these workflows, a single media reference contained within a clip becomes a dictionary of media references. Below is what the difference looks like when serialized. + +OTIO Representation without MMR + +```json +"media_reference" : { + "OTIO_SCHEMA": "ImageSequenceReference.1", + "metadata": {}, + "name": "", + "available_range": null, + "available_image_bounds": null, + "target_url_base": "/mnt/nvme/Projects/aProject/scene01", + "name_prefix": "opening.", + "name_suffix": ".exr", + "start_frame": 1, + "frame_step": 1, + "rate": 1.0, + "frame_zero_padding": 4, + "missing_frame_policy": "error" +} +``` + +OTIO Representation with MMR + +```json +"media_references": { + "Frames": { + "OTIO_SCHEMA": "ImageSequenceReference.1", + "metadata": {}, + "name": "", + "available_range": null, + "available_image_bounds": null, + "target_url_base": "/mnt/nvme/Projects/aProject/scene01", + "name_prefix": "opening.", + "name_suffix": ".exr", + "start_frame": 1, + "frame_step": 1, + "rate": 1.0, + "frame_zero_padding": 4, + "missing_frame_policy": "error" + }, + "Movie": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": {}, + "name": "", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 1300.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 1.0 + } + }, + "available_image_bounds": null, + "target_url": "/mnt/nvme/Projects/aProject/scene01/opening.mov" + }, + "Streaming": { + "OTIO_SCHEMA": "ExternalReference.1", + "metadata": {}, + "name": "", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 1300.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 1.0 + } + }, + "available_image_bounds": { + "OTIO_SCHEMA": "Box2d.1", + "min": { + "OTIO_SCHEMA": "V2d.1", + "x": -1.1764705882352942, + "y": -0.5 + }, + "max": { + "OTIO_SCHEMA": "V2d.1", + "x": 1.1764705882352942, + "y": 0.5 + } + }, + "target_url": "https://acme.shotguncloud.com/file_serve/version/23088/mp4" + } +}, +"active_media_reference_key": "Streaming" +``` + +RV Representation without MMR + +![no switch group](../images/rv-manuals-media-multi-representation-scheme-no-switch-group.png) + +RV Representation with MMR + +![with switch group](../images/rv-manuals-media-multi-representation-scheme-with-switch-group.png) + +When you import an OTIO file in RV, you always end up with an RV SwitchGroup where `active_media_reference` sets the active RVSourceGroup. + +When you export from RV an OTIO file, every SwitchGroup node outputs as a Clip with its `active_media_reference_key` set to the currently active RVSourceGroup. diff --git a/packages/CMakeLists.txt b/packages/CMakeLists.txt new file mode 100644 index 000000000..8eba7b721 --- /dev/null +++ b/packages/CMakeLists.txt @@ -0,0 +1,12 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +IF(RV_TARGET_DARWIN) + ADD_SUBDIRECTORY(RV_app) +ELSE() + # assuming this is for Linux & Windows + ADD_SUBDIRECTORY(rv) +ENDIF() diff --git a/packages/RV_app/CMakeLists.txt b/packages/RV_app/CMakeLists.txt new file mode 100644 index 000000000..02fd088db --- /dev/null +++ b/packages/RV_app/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIGURE_FILE(rvrc.mu ${RV_STAGE_RESOURCES_ENGLISH_DIR} COPYONLY) +CONFIGURE_FILE(init.mu ${RV_STAGE_RESOURCES_ENGLISH_DIR} COPYONLY) +CONFIGURE_FILE(rviorc.mu ${RV_STAGE_RESOURCES_ENGLISH_DIR} COPYONLY) +CONFIGURE_FILE(rvrc.py ${RV_STAGE_RESOURCES_ENGLISH_DIR} COPYONLY) +CONFIGURE_FILE(qt.conf ${RV_STAGE_RESOURCES_DIR} COPYONLY) diff --git a/packages/RV_app/init.mu b/packages/RV_app/init.mu new file mode 100644 index 000000000..df40570e0 --- /dev/null +++ b/packages/RV_app/init.mu @@ -0,0 +1,29 @@ +require rvui; + +documentation: +"Called when a session is first created"; + +\: initialize (object;) +{ + // + // To override default bindings just set them after you call this, + // otherwise, you need to provide all of the bindings if you + // replace it. + // + + rvui.defineDefaultBindings(); + + // + // You can add to rvui.mainMenu before calling this + // + + defineModeMenu("default", rvui.buildMainMenu()); + + // + // Make a new State object. Any object can be returned here + // (tuple, etc). In this case we're going to provide the default + // State object. + // + + return rvui.newStateObject(); +} diff --git a/packages/RV_app/qt.conf b/packages/RV_app/qt.conf new file mode 100644 index 000000000..de260fa00 --- /dev/null +++ b/packages/RV_app/qt.conf @@ -0,0 +1,2 @@ +[Paths] +Plugins = PlugIns/Qt diff --git a/packages/RV_app/rviorc.mu b/packages/RV_app/rviorc.mu new file mode 100644 index 000000000..8db8c9517 --- /dev/null +++ b/packages/RV_app/rviorc.mu @@ -0,0 +1,10 @@ +//require rvui; + +documentation: +"Called when a session is first created"; + +\: initialize (object;) +{ + //return rvui.newStateObject(); + return nil; +} diff --git a/packages/RV_app/rvrc.mu b/packages/RV_app/rvrc.mu new file mode 100644 index 000000000..df40570e0 --- /dev/null +++ b/packages/RV_app/rvrc.mu @@ -0,0 +1,29 @@ +require rvui; + +documentation: +"Called when a session is first created"; + +\: initialize (object;) +{ + // + // To override default bindings just set them after you call this, + // otherwise, you need to provide all of the bindings if you + // replace it. + // + + rvui.defineDefaultBindings(); + + // + // You can add to rvui.mainMenu before calling this + // + + defineModeMenu("default", rvui.buildMainMenu()); + + // + // Make a new State object. Any object can be returned here + // (tuple, etc). In this case we're going to provide the default + // State object. + // + + return rvui.newStateObject(); +} diff --git a/packages/RV_app/rvrc.py b/packages/RV_app/rvrc.py new file mode 100644 index 000000000..dd9c4154b --- /dev/null +++ b/packages/RV_app/rvrc.py @@ -0,0 +1,10 @@ +import rv.rvui + + +def initialize(): + rv.rvui.defineDefaultBindings() + return rv.rvui.newStateObject() + + +def setup(): + return diff --git a/packages/rv/CMakeLists.txt b/packages/rv/CMakeLists.txt new file mode 100644 index 000000000..e9553eaf5 --- /dev/null +++ b/packages/rv/CMakeLists.txt @@ -0,0 +1,32 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +SET(_target + "package-rv" +) + +ADD_CUSTOM_TARGET(${_target}) + +# Right now this is mostly to copy to the build folder +CONFIGURE_FILE(rvrc.mu ${RV_STAGE_SCRIPTS_DIR}/rv/rvrc.mu COPYONLY) +CONFIGURE_FILE(rviorc.mu ${RV_STAGE_SCRIPTS_DIR}/rv/rviorc.mu COPYONLY) +CONFIGURE_FILE(rvrc.py ${RV_STAGE_SCRIPTS_DIR}/rv/rvrc.py COPYONLY) +CONFIGURE_FILE(qt.conf ${RV_STAGE_BIN_DIR}/qt.conf COPYONLY) + +IF(RV_TARGET_WINDOWS) + FILE( + GLOB regfiles + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + *.reg + ) + FOREACH( + regfile + ${regfiles} + ) + CONFIGURE_FILE(${regfile} ${RV_APP_ROOT}/etc/${regfile} COPYONLY) + ENDFOREACH() + CONFIGURE_FILE(README.regfiles ${RV_APP_ROOT}/etc/README.regfiles COPYONLY) +ENDIF() diff --git a/packages/rv/README b/packages/rv/README new file mode 100644 index 000000000..23c742b2c --- /dev/null +++ b/packages/rv/README @@ -0,0 +1,30 @@ + +ADDING AN IMAGE FILE FORMAT +--------------------------- + +RV's built-in JPEG reader/writer is provided as source code in the +src/io_jpeg_sample directory. +You should be able to recompile the plugin yourself. + +Use it as a template for your own image formats. + + +ADDING A MOVIE FILE FORMAT +-------------------------- + +Because movie file I/O tends to be more complex than image file I/O +we provide a very simple contrived example in src/mio_moovee_sample. + + +CUSTOMIZING RV'S USER INTERFACE SYSTEM WIDE +------------------------------------------- + +Most of RV's user interface is implemented in the file +plugins/Mu/rvui.mu. You can hack this file to change the behavior +and/or look of RV. + + +PERSONAL CUSTOMIZATION OF RV'S UI +--------------------------------- + +Copy scripts/rv/rvrc.mu to ~/.rvrc.mu and hack away. diff --git a/packages/rv/README.regfiles b/packages/rv/README.regfiles new file mode 100644 index 000000000..4e724eed1 --- /dev/null +++ b/packages/rv/README.regfiles @@ -0,0 +1,42 @@ + +The "standard" windows registry files here are: + + openwith.reg + + Launch RV when double-clicking on media files. + + rvlink.reg + + Register RV as the handler for the "rvlink://" custom protocol. Docs on the rvlink protocol here: + + https://github.com/AcademySoftwareFoundation/OpenRV/blob/main/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-c.md + + +The "rvpush" .reg files are alternates to support different workflows. RVPUSH +is an executable we provide that allows you to communicate with a running RV +from the command line. Docs here: + + https://github.com/AcademySoftwareFoundation/OpenRV/blob/main/doc/rv-manuals/rv-user-manual/rv-user-manual-chapter-eighteen.md + +It's a general utility, but it's used here in the .reg files to avoid the RV +startup time when loading media by using a running RV if one is available. + +In particular: + + rvlink_rvpush.reg + + This one uses RVPUSH to handle URLs, IE the first one will open a new + RV (tagged as the URL handler) and if that RV is still running when + next you click on a URL, that will be handled by the running RV instead + of a new one. + + openwith_rvpush_set.reg + + Similar to the above, but for the "double click on a file" workflow. + + openwith_rvpush_merge.reg + + This uses the "merge" action instead of the "set" action for RVPUSH, + which means that double-clicking on a second file after you have + already opened one will merge that media into the running RV instead of + replacing the previous media. diff --git a/packages/rv/formats.gto b/packages/rv/formats.gto new file mode 100644 index 000000000..55bfa8066 --- /dev/null +++ b/packages/rv/formats.gto @@ -0,0 +1,193 @@ +GTOa (3) + +"GraphicsMagickPlugin.so" : imageio (1) +{ + avs + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + bmp + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + dcm + { + string description = "" + int capabilities = 4 + string[2] codecs = [ ] + } + + dcx + { + string description = "" + int capabilities = 4 + string[2] codecs = [ ] + } + + dib + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + fits + { + string description = "" + int capabilities = 1548 + string[2] codecs = [ ] + } + + gif + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + gif87 + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + mat + { + string description = "" + int capabilities = 4 + string[2] codecs = [ ] + } + + miff + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + pbm + { + string description = "" + int capabilities = 4 + string[2] codecs = [ ] + } + + pict + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + pix + { + string description = "" + int capabilities = 4 + string[2] codecs = [ ] + } + + ppm + { + string description = "" + int capabilities = 4 + string[2] codecs = [ ] + } + + psd + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + rla + { + string description = "" + int capabilities = 4 + string[2] codecs = [ ] + } + + rle + { + string description = "" + int capabilities = 4 + string[2] codecs = [ ] + } + + sgi + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + sun + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + tga + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + tim + { + string description = "" + int capabilities = 4 + string[2] codecs = [ ] + } + + viff + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + wbmp + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + wpg + { + string description = "" + int capabilities = 4 + string[2] codecs = [ ] + } + + xbm + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } + + xcf + { + string description = "" + int capabilities = 4 + string[2] codecs = [ ] + } + + xpm + { + string description = "" + int capabilities = 524 + string[2] codecs = [ ] + } +} diff --git a/packages/rv/openwith.reg b/packages/rv/openwith.reg new file mode 100644 index 000000000..9870c797d Binary files /dev/null and b/packages/rv/openwith.reg differ diff --git a/packages/rv/openwith_rvpush_merge.reg b/packages/rv/openwith_rvpush_merge.reg new file mode 100644 index 000000000..79554f109 Binary files /dev/null and b/packages/rv/openwith_rvpush_merge.reg differ diff --git a/packages/rv/openwith_rvpush_set.reg b/packages/rv/openwith_rvpush_set.reg new file mode 100644 index 000000000..7c67eef63 Binary files /dev/null and b/packages/rv/openwith_rvpush_set.reg differ diff --git a/packages/rv/qt.conf b/packages/rv/qt.conf new file mode 100644 index 000000000..45723ff62 --- /dev/null +++ b/packages/rv/qt.conf @@ -0,0 +1,4 @@ +[Paths] +Prefix = .. +Plugins = plugins/Qt + diff --git a/packages/rv/rv.install_handler_linux b/packages/rv/rv.install_handler_linux new file mode 100755 index 000000000..3af343351 --- /dev/null +++ b/packages/rv/rv.install_handler_linux @@ -0,0 +1,118 @@ +#!/bin/tcsh -f +# +# +# This script attempts to register RV as the protocol hander for +# links that look like rvlink://blah. +# +# It should be sufficient for gnome apps like pidgin and kde apps +# like konqueror. Firefox seems to pay attention to the gnome +# settings at least to the degree that it recognizes links of the +# form rvlink://blah as hot-links, but it may still ask you to +# select the application the first time you click on one. +# +# We (tweak) are not expert on this stuff, so if you have ideas +# about how to do this better or more completely, please let us +# know ! (support@tweakadmin.com) +# + +if (! $?RV_HOME) then + pushd `dirname $0` >& /dev/null + setenv RV_HOME `dirname ${cwd}` + popd >& /dev/null +endif + +set rv = "$RV_HOME/bin/rv" + +# +# Gnome: register rv as rvlink protocol handler +# +echo "Installing rvlink protocol handler for Gnome." + +set gconfTool = "`which gconftool-2`" + +if ($status == 0) then + gconftool-2 --set --type=string /desktop/gnome/url-handlers/rvlink/command $rv' "%s"' + gconftool-2 --set --type=bool /desktop/gnome/url-handlers/rvlink/enabled true + gconftool-2 --set --type=bool /desktop/gnome/url-handlers/rvlink/need-terminal false +else + echo "WARNING: gconftool-2 not found: skipping gnome url-handler registration." +endif + +# +# KDE: register rv as rvlink protocol handler +# If you want to install this system wide copy the rvlink.protocol file to /usr/share/kde4/services/ +# +echo "Installing rvlink protocol handler for KDE." + +set kdeProtoDir = ~/.kde/share/services +if ( -e ~/.kde4/share/services ) then + set kdeProtoDir = ~/.kde4/share/services +endif +if (${?KDEDIR}) then + set kdeProtoDir = ${KDEDIR}/share/services +endif + +if ( ! -e $kdeProtoDir) then + mkdir -p $kdeProtoDir +endif + +if ( -e $kdeProtoDir) then + set kdeProtoFile = $kdeProtoDir/rvlink.protocol + rm -f $kdeProtoFile + cat > $kdeProtoFile << EOF +[Protocol] +exec=$rv "%u" +protocol=rvlink +input=none +output=none +helper=true +listing=false +reading=false +writing=false +makedir=false +deleting=false +EOF + echo "Successfully created ${kdeProtoFile}" +else + echo "WARNING: can't find or create KDE protocol directory: ${kdeProtoDir}: skipping KDE url-handler registration." +endif + +# +# Gnome part 2 for xdg/Chrome: register rv as rvlink protocol handler +# If you want to install this system wide run the desktop-file-install as sudo/root +# and remove the --dir= bit. +# +echo "Installing rvlink protocol handler for XDG" + +set desktopInstall = "`which desktop-file-install`" + +if ($status == 0) then + set xdgDir = $HOME/.local/share/applications + if ( ! -e $xdgDir ) then + mkdir -p $xdgDir + endif + + if ( -e $xdgDir) then + set xdgFile = $xdgDir/rv.desktop + rm -f $xdgFile + cat > $xdgFile << EOF +[Desktop Entry] +Name=RVLink +Type=Application +Exec=$rv %U +Terminal=false +Categories=AudioVideo;Viewer;Player; +MimeType=x-scheme-handler/rvlink; +NoDisplay=true +EOF + echo "Successfully created ${xdgFile}" + else + echo "WARNING: can't find or create XDG directory: ${xdgDir}: skipping XDG url-handler registration." + endif + + desktop-file-install $xdgFile --rebuild-mime-info-cache --dir=$xdgDir +else + echo "WARNING: desktop-file-install not found: skipping xdg-handler registration." +endif + +echo "Done." diff --git a/packages/rv/rv.install_handler_linux_rvpush b/packages/rv/rv.install_handler_linux_rvpush new file mode 100755 index 000000000..f8a23aa0d --- /dev/null +++ b/packages/rv/rv.install_handler_linux_rvpush @@ -0,0 +1,118 @@ +#!/bin/tcsh -f +# +# +# This script attempts to register RV as the protocol hander for +# links that look like rvlink://blah. +# +# It should be sufficient for gnome apps like pidgin and kde apps +# like konqueror. Firefox seems to pay attention to the gnome +# settings at least to the degree that it recognizes links of the +# form rvlink://blah as hot-links, but it may still ask you to +# select the application the first time you click on one. +# +# We (tweak) are not expert on this stuff, so if you have ideas +# about how to do this better or more completely, please let us +# know ! (support@tweakadmin.com) +# + +if (! $?RV_HOME) then + pushd `dirname $0` >& /dev/null + setenv RV_HOME `dirname ${cwd}` + popd >& /dev/null +endif + +set rvpush = "$RV_HOME/bin/rvpush" + +# +# Gnome: register rv as rvlink protocol handler +# +echo "Installing rvlink protocol handler for Gnome." + +set gconfTool = "`which gconftool-2`" + +if ($status == 0) then + gconftool-2 --set --type=string /desktop/gnome/url-handlers/rvlink/command $rvpush' -tag rvlink url "%s"' + gconftool-2 --set --type=bool /desktop/gnome/url-handlers/rvlink/enabled true + gconftool-2 --set --type=bool /desktop/gnome/url-handlers/rvlink/need-terminal false +else + echo "WARNING: gconftool-2 not found: skipping gnome url-handler registration." +endif + +# +# KDE: register rv as rvlink protocol handler +# If you want to install this system wide copy the rvlink.protocol file to /usr/share/kde4/services/ +# +echo "Installing rvlink protocol handler for KDE." + +set kdeProtoDir = ~/.kde/share/services +if ( -e ~/.kde4/share/services ) then + set kdeProtoDir = ~/.kde4/share/services +endif +if (${?KDEDIR}) then + set kdeProtoDir = ${KDEDIR}/share/services +endif + +if ( ! -e $kdeProtoDir) then + mkdir -p $kdeProtoDir +endif + +if ( -e $kdeProtoDir) then + set kdeProtoFile = $kdeProtoDir/rvlink.protocol + rm -f $kdeProtoFile + cat > $kdeProtoFile << EOF +[Protocol] +exec=$rvpush -tag rvlink url "%u" +protocol=rvlink +input=none +output=none +helper=true +listing=false +reading=false +writing=false +makedir=false +deleting=false +EOF + echo "Successfully created ${kdeProtoFile}" +else + echo "WARNING: can't find or create KDE protocol directory: ${kdeProtoDir}: skipping KDE url-handler registration." +endif + +# +# Gnome part 2 for xdg/Chrome: register rv as rvlink protocol handler +# If you want to install this system wide run the desktop-file-install as sudo/root +# and remove the --dir= bit. +# +echo "Installing rvlink protocol handler for XDG" + +set desktopInstall = "`which desktop-file-install`" + +if ($status == 0) then + set xdgDir = $HOME/.local/share/applications + if ( ! -e $xdgDir ) then + mkdir -p $xdgDir + endif + + if ( -e $xdgDir) then + set xdgFile = $xdgDir/rv.desktop + rm -f $xdgFile + cat > $xdgFile << EOF +[Desktop Entry] +Name=RVLink +Type=Application +Exec=$rvpush -tag rvlink url %U +Terminal=false +Categories=AudioVideo;Viewer;Player; +MimeType=x-scheme-handler/rvlink; +NoDisplay=true +EOF + echo "Successfully created ${xdgFile}" + else + echo "WARNING: can't find or create XDG directory: ${xdgDir}: skipping XDG url-handler registration." + endif + + desktop-file-install $xdgFile --rebuild-mime-info-cache --dir=$xdgDir +else + echo "WARNING: desktop-file-install not found: skipping xdg-handler registration." +endif + +echo "Done." diff --git a/packages/rv/rviorc.mu b/packages/rv/rviorc.mu new file mode 100644 index 000000000..8db8c9517 --- /dev/null +++ b/packages/rv/rviorc.mu @@ -0,0 +1,10 @@ +//require rvui; + +documentation: +"Called when a session is first created"; + +\: initialize (object;) +{ + //return rvui.newStateObject(); + return nil; +} diff --git a/packages/rv/rvlink.reg b/packages/rv/rvlink.reg new file mode 100644 index 000000000..cbc265ba0 Binary files /dev/null and b/packages/rv/rvlink.reg differ diff --git a/packages/rv/rvlink_rvpush.reg b/packages/rv/rvlink_rvpush.reg new file mode 100644 index 000000000..53282b607 Binary files /dev/null and b/packages/rv/rvlink_rvpush.reg differ diff --git a/packages/rv/rvrc.mu b/packages/rv/rvrc.mu new file mode 100644 index 000000000..df40570e0 --- /dev/null +++ b/packages/rv/rvrc.mu @@ -0,0 +1,29 @@ +require rvui; + +documentation: +"Called when a session is first created"; + +\: initialize (object;) +{ + // + // To override default bindings just set them after you call this, + // otherwise, you need to provide all of the bindings if you + // replace it. + // + + rvui.defineDefaultBindings(); + + // + // You can add to rvui.mainMenu before calling this + // + + defineModeMenu("default", rvui.buildMainMenu()); + + // + // Make a new State object. Any object can be returned here + // (tuple, etc). In this case we're going to provide the default + // State object. + // + + return rvui.newStateObject(); +} diff --git a/packages/rv/rvrc.py b/packages/rv/rvrc.py new file mode 100644 index 000000000..dd9c4154b --- /dev/null +++ b/packages/rv/rvrc.py @@ -0,0 +1,10 @@ +import rv.rvui + + +def initialize(): + rv.rvui.defineDefaultBindings() + return rv.rvui.newStateObject() + + +def setup(): + return diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..ae4133b09 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +# This file contains all of RV's python build requirements. They are NOT packaged with the app. + +pyyaml # License: MIT License (MIT) +requests # License: Apache Software License (Apache 2.0) +wheel # License: MIT License (MIT) +py7zr # License: GNU Lesser General Public License v2 or later (LGPLv2+) (LGPL-2.1-or-later) +dohq-artifactory # License: MIT License (MIT License) +pre-commit # License: MIT License (MIT) diff --git a/rvcmds.sh b/rvcmds.sh new file mode 100755 index 000000000..a4348ef6f --- /dev/null +++ b/rvcmds.sh @@ -0,0 +1,85 @@ +# Ensure file is sourced and not executed + +SOURCED=0 +if [ -n "$ZSH_EVAL_CONTEXT" ]; then + [[ $ZSH_EVAL_CONTEXT =~ :file$ ]] && SOURCED=1 +elif [ -n "$KSH_VERSION" ]; then + [[ "$(cd $(dirname -- $0) && pwd -P)/$(basename -- $0)" != "$(cd $(dirname -- ${.sh.file}) && pwd -P)/$(basename -- ${.sh.file})" ]] && SOURCED=1 +elif [ -n "$BASH_VERSION" ]; then + [[ $0 != "$BASH_SOURCE" ]] && SOURCED=1 +elif grep -q dash /proc/$$/cmdline; then + case $0 in *dash*) SOURCED=1 ;; esac +fi + +if [[ $SOURCED == 0 ]]; then + echo "Please call: source $BASH_SOURCE" + exit 1 +fi + + +QT_VERSION="${QT_VERSION:-5.15.2}" +if [[ "$OSTYPE" == "linux"* ]]; then + CMAKE_GENERATOR="${CMAKE_GENERATOR:-Ninja}" + QT_HOME="${QT_HOME:-$HOME/Qt${QT_VERSION}/${QT_VERSION}/gcc_64}" + RV_TARGET=rv + SCRIPT_HOME=`readlink -f $(dirname $0)` +elif [[ "$OSTYPE" == "darwin"* ]]; then + CMAKE_GENERATOR="${CMAKE_GENERATOR:-Ninja}" + QT_HOME="${QT_HOME:-$HOME/Qt${QT_VERSION}/${QT_VERSION}/clang_64}" + RV_TARGET=RV + SCRIPT_HOME=`readlink -f $(dirname $0)` +elif [[ "$OSTYPE" == "msys"* ]]; then + CMAKE_GENERATOR="${CMAKE_GENERATOR:-Visual Studio 16 2019}" + QT_HOME="${QT_HOME:-c:/Qt/Qt${QT_VERSION}/${QT_VERSION}/msvc2019_64}" + WIN_PERL="${WIN_PERL:-c:/Strawberry/perl/bin}" + CMAKE_WIN_ARCH="${CMAKE_WIN_ARCH:--A x64}" + RV_TARGET=rv + SCRIPT_HOME=`readlink -f $(dirname ${BASH_SOURCE[0]})` +else + echo "OS does not seem to be linux, darwin or msys. Exiting." + exit 1 +fi + +# VARIABLES +RV_HOME="${RV_HOME:-$SCRIPT_HOME}" +RV_BUILD="${RV_BUILD:-${RV_HOME}/_build}" +RV_INST="${RV_INST:-${RV_HOME}/install}" + +# ALIASES: Basic commands + +alias rvsetup="python3 -m pip install --user --upgrade -r ${RV_HOME}/requirements.txt" +alias rvcfg="cmake -B ${RV_BUILD} -G \"${CMAKE_GENERATOR}\" ${CMAKE_WIN_ARCH} -DCMAKE_BUILD_TYPE=Release -DRV_DEPS_QT5_LOCATION=${QT_HOME} -DRV_DEPS_WIN_PERL_ROOT=${WIN_PERL}" +alias rvcfgd="cmake -B ${RV_BUILD} -G \"${CMAKE_GENERATOR}\" ${CMAKE_WIN_ARCH} -DCMAKE_BUILD_TYPE=Debug -DRV_DEPS_QT5_LOCATION=${QT_HOME} -DRV_DEPS_WIN_PERL_ROOT=${WIN_PERL}" +alias rvbuild="cmake --build ${RV_BUILD} --config Release --target ${RV_TARGET} --parallel -v" +alias rvbuildd="cmake --build ${RV_BUILD} --config Debug --target ${RV_TARGET} --parallel -v" +alias rvbuildt="cmake --build ${RV_BUILD} --config Release --target " +alias rvbuildtd="cmake --build ${RV_BUILD} --config Debug --target " +alias rvtest="ctest --test-dir ${RV_BUILD} --extra=verbose" +alias rvinst="cmake --install ${RV_BUILD} --prefix ${RV_INST}" +alias rvclean="rm -rf ${RV_BUILD}" + +# ALIASES: Config and Build + +alias rvmk="rvcfg && rvbuild" +alias rvmkd="rvcfgd && rvbuildd" + +# ALIASES: Setup, Config and Build + +alias rvbootstrap="rvsetup && rvmk" +alias rvbootstrapd="rvsetup && rvmkd" + +echo "Please ensure you have installed any required dependencies from doc/build_system/config_[os]" +echo +echo "CMake parameters:" + +echo "RV_HOME is $RV_HOME" +echo "RV_BUILD is $RV_BUILD" +echo "RV_INST is $RV_INST" +echo "CMAKE_GENERATOR is $CMAKE_GENERATOR" +echo "QT_HOME is $QT_HOME" +if [[ "$OSTYPE" == "msys"* ]]; then echo "WIN_PERL is $WIN_PERL"; fi + +echo "To override any of them do unset [name]; export [name]=value; source $0" +echo +echo "If this is your first time building RV try rvbootstrap (release) or rvbootstrapd (debug)" +echo "To build quickly after bootstraping try rvmk (release) or rvmkd (debug)" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 000000000..eaebf4888 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +ADD_SUBDIRECTORY(plugins) +ADD_SUBDIRECTORY(lib) +ADD_SUBDIRECTORY(pub) +ADD_SUBDIRECTORY(bin) +ADD_SUBDIRECTORY(test) diff --git a/src/bin/CMakeLists.txt b/src/bin/CMakeLists.txt new file mode 100644 index 000000000..e9e8d1f13 --- /dev/null +++ b/src/bin/CMakeLists.txt @@ -0,0 +1,14 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +ADD_SUBDIRECTORY(apps) +ADD_SUBDIRECTORY(mu) +IF(RV_TARGET_DARWIN) + ADD_SUBDIRECTORY(nsapps) +ENDIF() +ADD_SUBDIRECTORY(gtotools) +ADD_SUBDIRECTORY(imgtools) +ADD_SUBDIRECTORY(python) diff --git a/src/bin/apps/CMakeLists.txt b/src/bin/apps/CMakeLists.txt new file mode 100644 index 000000000..37690a1cd --- /dev/null +++ b/src/bin/apps/CMakeLists.txt @@ -0,0 +1,17 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +ADD_SUBDIRECTORY(rvshell) + +IF(RV_TARGET_LINUX + OR RV_TARGET_WINDOWS +) + ADD_SUBDIRECTORY(rv) +ENDIF() + +ADD_SUBDIRECTORY(rvpush) +ADD_SUBDIRECTORY(rvpkg) +ADD_SUBDIRECTORY(rvprof) diff --git a/src/bin/apps/rv/CMakeLists.txt b/src/bin/apps/rv/CMakeLists.txt new file mode 100644 index 000000000..a1ddeedb5 --- /dev/null +++ b/src/bin/apps/rv/CMakeLists.txt @@ -0,0 +1,114 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "rv" +) + +SET(CMAKE_AUTOUIC + ON +) +SET(CMAKE_AUTOMOC + ON +) +SET(CMAKE_AUTORCC + ON +) + +FIND_PACKAGE( + Qt5 + COMPONENTS Core Gui Widgets OpenGL + REQUIRED +) + +SET(SOURCES + main.cpp utf8WinMain.cpp +) +IF(RV_TARGET_WINDOWS) + LIST(APPEND SOURCES RV.RC) +ENDIF() + +QT5_ADD_RESOURCES(_rv_qrc rv.qrc) + +ADD_EXECUTABLE( + ${_target} + ${SOURCES} ${_rv_qrc} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +IF(RV_TARGET_LINUX) + SET(THREADS_PREFER_PTHREAD_FLAG + TRUE + ) + FIND_PACKAGE(Threads REQUIRED) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE Threads::Threads + ) +ENDIF() + +IF(NOT RV_TARGET_WINDOWS) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE Qt::OpenGL + ) +ENDIF() + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE RvCommon + IOproxy + MovieProxy + MovieSideCar + OpenEXR::OpenEXR + MovieFB + MovieProcedural + MuGL + MuIO + MuTwkApp + PyTwkApp + IPCore + RvApp + RvPackage + TwkApp + TwkDeploy + QTBundle + TwkQtCoreUtil + TwkExc + TwkFB + TwkGLFFBO + TwkMovie + TwkUtil + TwkGLF + arg + stl_ext +) + +IF(RV_TARGET_WINDOWS) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE glew win_pthreads Shlwapi + ) +ENDIF() + +TARGET_COMPILE_OPTIONS( + ${_target} + PRIVATE -D__STDC_FORMAT_MACROS + -DGIT_HEAD=\"${RV_GIT_COMMIT_SHORT_HASH}\" + -DUSE_SIDECARS=1 + -DMAJOR_VERSION=${RV_MAJOR_VERSION} + -DMINOR_VERSION=${RV_MINOR_VERSION} + -DREVISION_NUMBER=${RV_REVISION_NUMBER} + -DRELEASE_DESCRIPTION=\"${RV_RELEASE_DESCRIPTION}\" +) + +RV_STAGE(TYPE "MAIN_EXECUTABLE" TARGET ${_target}) diff --git a/src/bin/apps/rv/RV.RC b/src/bin/apps/rv/RV.RC new file mode 100644 index 000000000..fed4eb067 --- /dev/null +++ b/src/bin/apps/rv/RV.RC @@ -0,0 +1,2 @@ +IDI_ICON1 ICON DISCARDABLE "RV.ico" + diff --git a/src/bin/apps/rv/RV.ico b/src/bin/apps/rv/RV.ico new file mode 100644 index 000000000..a312f93a4 Binary files /dev/null and b/src/bin/apps/rv/RV.ico differ diff --git a/src/bin/apps/rv/main.cpp b/src/bin/apps/rv/main.cpp new file mode 100644 index 000000000..4095fe284 --- /dev/null +++ b/src/bin/apps/rv/main.cpp @@ -0,0 +1,811 @@ +//****************************************************************************** +// Copyright (c) 2001-2005 Tweak Inc. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifdef _MSC_VER + // + // We are targetting at least XP, (IE no windows95, etc). + // + #define WINVER 0x0501 + #define _WIN32_WINNT 0x0501 + #include + #include + #include + #include + #include + // + // NOTE: win_pthreads, which supplies implement.h, seems + // targetted at an earlier version of windows (pre-XP). If you + // include implement.h here, it won't compile. But as far as I + // can tell, it's not needed, so just leave it out. + // + // #include +#endif + +#ifdef PLATFORM_LINUX +#include +#endif +#ifndef PLATFORM_LINUX +#include +#endif +#include +#include "../../utf8Main.h" +#include +#include +#include +#include +#include +#include +#ifdef PLATFORM_DARWIN +#include "pool.h" +#endif +#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 +#include +#include +#include +#include + +#ifndef PLATFORM_WINDOWS + #include + #include + #include +#else + #include + #include + #include +#endif + +#include +#include +#include + +extern const char* rv_linux_dark; + +// +// Check that a version actually exists. If we're compiling opt error +// out in cpp. Otherwise, just note the lack of version info. +// + +#if MAJOR_VERSION == 99 +#if NDEBUG +//#error ********* NO VERSION INFORMATION *********** +#else +#ifndef _MSC_VER +#warning ********* NO VERSION INFORMATION *********** +#endif +#endif +#endif + +#ifdef WIN32 +#define FILE_SEP ";" +#else +#define FILE_SEP ":" +#endif + +using namespace std; +using namespace TwkFB; +using namespace TwkMovie; +using namespace TwkUtil; + +extern const char* TweakDark; + +#if !defined(PLATFORM_WINDOWS) +static void control_c_handler(int sig) +{ + cout << "INFO: stopped by user" << endl; + exit(1); +} +#endif + +static void showDevices(TwkApp::VideoModule* m) +{ + for (auto d : m->devices()) cout << "INFO: " << m->name() << " found " << d->name() << endl; +} + +void createDevices(Rv::RvDocument* doc, Rv::RvApplication* app) +{ + // This is where video output devices are created (optional) +} + +void setEnvVar(const string& var, const string& val) +{ +#ifdef WIN32 + ostringstream str; + str << var << "=" << val; + putenv(str.str().c_str()); +#else + setenv(var.c_str(), val.c_str(), 1); +#endif +} + +void setEnvVar(const string& var, const QFileInfo& val) +{ + setEnvVar(var, val.absoluteFilePath().toUtf8().data()); +} + +void addToEnvVar(const string& var, const string& val) +{ + if (getenv(var.c_str())) + { + ostringstream str; + str << getenv(var.c_str()) << FILE_SEP << val; + setEnvVar(var, str.str()); + } + else + { + setEnvVar(var, val); + } +} + +string scarfFile(const string& fileName) +{ + ifstream in(fileName.c_str(), ios::binary); + stringstream buffer; + buffer << in.rdbuf(); + return buffer.str(); +} + +#define EXECUTABLE_SHORT_NAME "rv" +#define EXECUTABLE_SHORT_NAME_CAPS "RV" + +TwkApp::QTBundle bundle(EXECUTABLE_SHORT_NAME, MAJOR_VERSION, MINOR_VERSION, REVISION_NUMBER); + +#ifdef PLATFORM_LINUX +extern "C" int XInitThreads(); +#endif + +int +utf8Main(int argc, char *argv[]) +{ +#ifdef PLATFORM_LINUX + XInitThreads(); +#endif + + const char* pythonPath = getenv("PYTHONPATH"); + const char* pythonHome = getenv("PYTHONHOME"); + + if (pythonPath) pythonPath = strdup(pythonPath); + if (pythonHome) pythonHome = strdup(pythonHome); + + #ifndef PLATFORM_WINDOWS + // + // Check the per-process limit on open file descriptors and + // reset the soft limit to the hard limit. + // + struct rlimit rlim; + getrlimit (RLIMIT_NOFILE, &rlim); + rlim.rlim_cur = rlim.rlim_max; + setrlimit (RLIMIT_NOFILE, &rlim); + getrlimit (RLIMIT_NOFILE, &rlim); + if (rlim.rlim_cur < rlim.rlim_max) + { + cerr << "WARNING: unable to increase open file limit above " << rlim.rlim_cur << endl; + } + #else + _setmaxstdio(2048); + #endif + + #ifdef PLATFORM_WINDOWS + // + // RV is a "GUI" app, so by default we get no output in the + // console, so "rv.exe -help" doesn't work, etc. Here we + // reattach to the starting console, so we do get that + // output. + // + HANDLE hand = GetStdHandle(STD_OUTPUT_HANDLE); + + if ( (hand == INVALID_HANDLE_VALUE || + GetFileType(hand) == FILE_TYPE_UNKNOWN) && + AttachConsole(ATTACH_PARENT_PROCESS)) + { + freopen("CON", "w", stdout); + freopen("CON", "w", stderr); + } + #endif + + // + // Check for a valid HOME. If we are on Windows and HOME is not set then + // see if we can create one from concatenating HOMEDRIVE and HOMEPATH. + // + if (!getenv("HOME")) + { + #ifdef PLATFORM_WINDOWS + if (getenv("HOMEDRIVE") && getenv("HOMEPATH")) + { + string home = string(getenv("HOMEDRIVE")) + getenv("HOMEPATH"); + setEnvVar("HOME", home.c_str()); + cout << "INFO: Using '" << home << "' for $HOME." << endl; + } + else + #endif + { + cerr << "ERROR: $HOME is not set in the environment and is required." << endl; + exit(-1); + } + } + + if(getenv("LANG")) + { + std::string originalLocale = getenv("LANG"); + setEnvVar("ORIGINALLOCAL", originalLocale.c_str()); + } + else + { + setEnvVar("ORIGINALLOCAL", "en"); + } + setEnvVar("LANG", "C"); + setEnvVar("LC_ALL", "C"); + QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); + TwkFB::ThreadPool::initialize(); + + // Qt 5.12.1 specific + // Disable Qt Quick hardware rendering because QwebEngineView conflicts with QGLWidget + setEnvVar( "QT_QUICK_BACKEND", "software"); + +#if defined(PLATFORM_LINUX) + // Work around for Wacom Tablet issue on linux + // Note: This is a Qt 5.12.4 regression (https://bugreports.qt.io/browse/QTBUG-77826) + setEnvVar( "QT_XCB_TABLET_LEGACY_COORDINATES", "1"); +#endif + + // Prevent crash at startup when multithreaded upload is enabled + // (RV Preferences/Rendering/Multithread GPU Upload) + QApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); + + TwkUtil::MemPool::initialize(); + + string altPrefsPath; + for (size_t i = 0; i < argc; i++) + { + if (i < argc-1 && !strcmp(argv[i], "-prefsPath")) altPrefsPath = argv[i+1]; + } + Rv::RvApplication::initializeQSettings (altPrefsPath); + + bool noPrefs = false; + bool resetPrefs = false; + Q_INIT_RESOURCE(rv); + + // + // Expand any rvlink urls in argv + // + + vector newArgv; + + for (int i = 0; i < argc; ++i) + { + if (0 == strncmp ("rvlink://", argv[i], 9)) + { + Rv::RvApplication::parseURL (argv[i], newArgv); + } + else newArgv.push_back (argv[i]); + } + argv = &(newArgv[0]); + argc = newArgv.size(); + + for (size_t i = 0; i < argc; i++) + { + if (!strcmp(argv[i], "-noPrefs")) noPrefs = true; + else if (!strcmp(argv[i], "-resetPrefs")) resetPrefs = true; + } + for (size_t i = 0; i < argc; i++) + { + if (!strcmp(argv[i], "-bakeURL")) + { + string url = Rv::RvApplication::bakeCommandLineURL (argc, argv); + cerr << "Baked URL: " << url << endl; + exit(0); + } + if (!strcmp(argv[i], "-encodeURL")) + { + string url = Rv::RvApplication::encodeCommandLineURL (argc, argv); + cerr << "Encoded URL: " << url << endl; + exit(0); + } + } + + Rv::Options& opts = Rv::Options::sharedOptions(); + if (resetPrefs) Rv::RvPreferences::resetPreferencesFile(); + if (!noPrefs) Rv::RvPreferences::loadSettingsIntoOptions(opts); + else Rv::PackageManager::setIgnorePrefs(true); + Rv::Options::manglePerSourceArgs(argv, argc); + Rv::Options prefOpts = opts; + + // + // Additional options. These are QT UI look related. + // + + // + // Call the deploy functions + // + + TWK_DEPLOY_APP_OBJECT dobj(MAJOR_VERSION, + MINOR_VERSION, + REVISION_NUMBER, + argc, argv, + RELEASE_DESCRIPTION, + "HEAD=" GIT_HEAD); + + Imf::staticInitialize(); + + TwkFB::GenericIO::init(); // Initialize TwkFB::GenericIO plugins statics + TwkMovie::GenericIO::init(); // Initialize TwkMovie::GenericIO plugins statics + + IPCore::Application::cacheEnvVars(); + + // + // We handle the '--help' flag by changing it to '-help' for + // argparse to work. + // + for (int i = 0; i < argc; ++i) + { + if (!strcmp("--help", argv[i])) + { + strcpy(argv[i], "-help"); + break; + } + } + + // + // Parse cmd line args + // + + int strictlicense = 0; + char *prefsPath = 0; + int sleepTime = 0; + + if (arg_parse + (argc, argv, + "", "", + RV_ARG_EXAMPLES, + "", "", + RV_ARG_SEQUENCE_HELP, + "", "", + RV_ARG_SOURCE_OPTIONS(opts), + "", "", + "", ARG_SUBR(Rv::RvApplication::parseInFiles), "Input sequence patterns, images, movies, or directories ", + RV_ARG_PARSE_OPTIONS(opts), + "-strictlicense", ARG_FLAG(&strictlicense), "Exit rather than consume an RV license if no rvsolo licenses are available", + "-prefsPath %S", &prefsPath, "Alternate path to preferences directory", +#if defined(PLATFORM_LINUX) + "-scheduler %S", &opts.schedulePolicy, "Thread scheduling policy (may require root)", + "-priorities %d %d", &opts.displayPriority, &opts.audioPriority, "Set display and audio thread priorities (may require root)", +#endif + "-sleep %d", &sleepTime, "Sleep (in seconds) before starting to allow attaching debugger", + NULL) < 0) + { + exit(-1); + } + + // + // Qt itself (on linux) will consider the -geometry flag to be + // a traditional X11 geometry flag, which it's not, so prevent + // this by switching it to "geometri" if it's in argv. + // + for (int i = 0; i < argc; ++i) if (strcmp(argv[i], "-geometry") == 0) argv[i][8] = 'i'; + + if (sleepTime > 0) + { + cout << "INFO: sleeping " << sleepTime << " seconds" << endl; + std::this_thread::sleep_for( std::chrono::seconds( sleepTime ) ); + cout << "INFO: continuing after sleep" << endl; + } + + if (opts.showVersion) + { + cout << MAJOR_VERSION << "." << MINOR_VERSION << "." << REVISION_NUMBER << endl; + exit(0); + } + + // + // Desktop aware: being "desktop aware" may be causing problems + // on some systems + // + + QApplication::setDesktopSettingsAware(opts.qtdesktop == 1); + + // + // Set scheduling parameters for display thread + // + + if (opts.schedulePolicy) + { +#if defined(PLATFORM_LINUX) + sched_param sp; + memset(&sp, 0, sizeof(sched_param)); + sp.sched_priority = opts.displayPriority; + + unsigned int policy = SCHED_OTHER; + + if (!strcmp(opts.schedulePolicy, "SCHED_RR")) policy = SCHED_RR; + else if (!strcmp(opts.schedulePolicy, "SCHED_FIFO")) policy = SCHED_FIFO; + + if (policy != SCHED_OTHER) + { + if (sched_setscheduler(0, policy, &sp)) + { + cout << "ERROR: can't set thread priority" << endl; + } + } + else + { + if (setpriority(PRIO_PROCESS, 0, opts.displayPriority)) + { + cout << "ERROR: can't set thread priority" << endl; + } + } +#elif defined(PLATFORM_DARWIN) + // don't bother here ... use RV or RV64 instead +#else + // Uh... windoz +#endif + } + + + // + // Banners + // + + TWK_DEPLOY_SHOW_PROGRAM_BANNER(cout); + TWK_DEPLOY_SHOW_COPYRIGHT_BANNER(cout); + TWK_DEPLOY_SHOW_LOCAL_BANNER(cout); + + // + // Get CPU info and tell the EXR library to use them all + // + + if (opts.exrcpus > 0) + { + Imf::setGlobalThreadCount(opts.exrcpus); + } + else + { + Imf::setGlobalThreadCount(TwkUtil::SystemInfo::numCPUs() > 1 + ? (TwkUtil::SystemInfo::numCPUs()-1) + : 1); + } + + // + // Additional options. These are QT UI look related. + // + + // Required by the Live Review RV plugin React component's scroll bars + std::vector arguments( argv, argv + argc ); + static char enableOverlayScrollbar[] = "--enable-features=OverlayScrollbar"; + arguments.emplace_back( enableOverlayScrollbar ); + argc = static_cast( arguments.size() ); + argv = &arguments[0]; + + // + // Application + // + + QApplication* app = new QApplication(argc, argv); + + QTranslator* translator = new QTranslator(); + QLocale locale = QLocale(getenv("ORIGINALLOCAL")); + if (translator->load(locale, QLatin1String("i18n"), "_",QLatin1String(":/translations"))) { + app->installTranslator(translator); + } + + QDir dir(QCoreApplication::applicationDirPath()); + dir.cdUp(); + dir.cd("plugins"); + dir.cd("Qt"); + QApplication::addLibraryPath(dir.absolutePath()); + + Rv::RvApplication* rvapp = new Rv::RvApplication(argc, argv, createDevices); + + rvapp->setExecutableNameCaps ("RV"); + + app->installEventFilter(rvapp); + app->setQuitOnLastWindowClosed(true); + + // + // Get the bundle info so we can find resource files + // + // init priorities are: + // + // 1) -init script + // 2) $RV_INIT + // 3) $HOME/.rvrc.mu + // 4) $RV_HOME/scripts/rv/rvrc.mu + // 5) ./rvrc.mu + // + + if (bundle.top() == "") + { + cerr << "ERROR: can't locate RV home directory (is it installed in root?)" + << endl; + + exit(-1); + } + + bundle.setEnvVar("RV_PYTHONPATH_EXTERNAL", (pythonPath) ? pythonPath : ""); + bundle.setEnvVar("RV_PYTHONHOME_EXTERNAL", (pythonHome) ? pythonHome : ""); + + if (getenv("RV_APP_RVIO")) bundle.setEnvVar("RV_APP_RVIO_SET_BY_USER", "true"); + else bundle.setEnvVar("RV_APP_RVIO", bundle.executableFile("rvio")); + + bundle.setEnvVar("RV_APP_RV_SHORT_NAME", EXECUTABLE_SHORT_NAME); + bundle.setEnvVar("RV_APP_RV", bundle.executableFile(EXECUTABLE_SHORT_NAME)); + bundle.setEnvVar("RV_APP_MANUAL", bundle.resource("rv_manual", "pdf")); + bundle.setEnvVar("RV_APP_MANUAL_HTML", bundle.resource("rv_manual", "html")); + bundle.setEnvVar("RV_APP_SDI_MANUAL", bundle.resource("rvsdi_manual", "pdf")); + bundle.setEnvVar("RV_APP_SDI_MANUAL_HTML", bundle.resource("rvsdi_manual", "html")); + bundle.setEnvVar("RV_APP_REFERENCE_MANUAL", bundle.resource("rv_reference", "pdf")); + bundle.setEnvVar("RV_APP_REFERENCE_MANUAL_HTML", bundle.resource("rv_reference", "html")); + bundle.setEnvVar("RV_APP_MU_MANUAL", bundle.resource("mu", "pdf")); + bundle.setEnvVar("RV_APP_GTO_REFERENCE", bundle.resource("gto", "pdf")); + bundle.setEnvVar("RV_APP_RELEASE_NOTES", bundle.resource("rv_release_notes", "html")); + bundle.setEnvVar("RV_APP_LICENSES_NOTES", bundle.resource("rv_client_licenses", "html")); + TwkApp::Bundle::PathVector licfiles = bundle.licenseFiles("license", "gto"); + bundle.addPathToEnvVar("OIIO_LIBRARY_PATH", bundle.appPluginPath("OIIO")); + + // + // Find the init file + // + + string muInitFile = bundle.rcfile("rvrc", "mu", "RV_INIT"); + string pyInitFile = bundle.rcfile("rvrc", "py", "RV_PYINIT"); + bundle.setEnvVar("RV_APP_INIT", muInitFile.c_str()); + bundle.setEnvVar("RV_APP_PYINIT", pyInitFile.c_str()); + + if (opts.initscript) muInitFile = opts.initscript; + + string licVar = string(EXECUTABLE_SHORT_NAME_CAPS) + "_LICENSE_FILE"; + + if (! opts.licarg) opts.licarg = getenv(licVar.c_str()); + if (! opts.licarg) opts.licarg = getenv("TWEAK_LICENSE_FILE"); + + if (opts.licarg) + { + // + // Override license file from command line + // + + QFileInfo qfi(opts.licarg); + if (!qfi.isReadable()) + { + cerr << "ERROR: license file '" << opts.licarg << "' unreadable" << endl; + exit(-1); + } + licfiles.resize(1); + licfiles.front() = opts.licarg; + bundle.setEnvVar("RV_APP_USE_LICENSE_FILE", opts.licarg); + } + + try + { + TwkFB::loadProxyPlugins("TWK_FB_PLUGIN_PATH"); + TwkMovie::loadProxyPlugins("TWK_MOVIE_PLUGIN_PATH"); + } + catch (...) + { + cerr << "WARNING: a problem occured while loading plugins." << endl; + cerr << " some plugins may not have been loaded." << endl; + } + + + TwkMovie::GenericIO::addPlugin(new MovieFBIO()); + TwkMovie::GenericIO::addPlugin(new MovieProceduralIO()); + + // + // Compile the list of sequencable file extensions + // + + TwkFB::GenericIO::compileExtensionSet(predicateFileExtensions()); + + #ifdef PLATFORM_WINDOWS + DWORD targetClass = HIGH_PRIORITY_CLASS; + if (getenv("RV_REALTIME_PRIORITY_CLASS")) targetClass = REALTIME_PRIORITY_CLASS; + DWORD proClass1 = GetPriorityClass (GetCurrentProcess()); + if (0 == SetPriorityClass (GetCurrentProcess(), targetClass)) + { + cerr << "ERROR: SetPriorityClass failed, error " << GetLastError() << endl; + } + DWORD proClass2 = GetPriorityClass (GetCurrentProcess()); + if (proClass2 != targetClass) + { + cerr << "WARNING: failed to set Priority Class, class " << hex << showbase << + int(proClass1) << " -> " << int(proClass2) << noshowbase << dec << endl; + } + #endif + + // + // QT starts here + // + +#ifdef PTW32_STATIC_LIB + pthread_win32_process_attach_np(); + pthread_win32_process_attach_np(); +#endif + + //Rv::dumpApplicationPalette(); + Rv::initializeDefaultPalette(); + + if (!getenv("RV_DARK")) bundle.setEnvVar("RV_DARK", "", true); + + QString csstext; + + if (opts.qtcss) + { + string s = scarfFile(opts.qtcss); + csstext = s.c_str(); + } + else + { + csstext = QString(rv_linux_dark) + .arg(opts.fontSize1) + .arg(opts.fontSize2) + .arg(opts.fontSize2-1); + } + + if (!opts.qtstyle || !strcmp(opts.qtstyle, "RV")) + { + bundle.setEnvVar("RV_DARK", "1", true); + app->setStyle(QStyleFactory::create("Cleanlooks")); + } + else + { + app->setStyle(QStyleFactory::create(opts.qtstyle)); + } + + app->setStyleSheet(csstext); + + try + { + TwkApp::initMu(0); + TwkApp::initPython(argc, argv); + Rv::initUICommands(); + Rv::initCommands(); + + if (!opts.initializeAfterParsing((noPrefs) ? 0 : &prefOpts)) + { + rvapp->console()->processLastTextBuffer(); + exit(-1); + } + + TwkApp::initWithFile(TwkApp::muContext(), + TwkApp::muProcess(), + TwkApp::muModuleList(), + muInitFile.c_str()); + + TwkApp::pyInitWithFile(pyInitFile.c_str(), Rv::pyRvAppCommands(), Rv::pyUICommands()); + } + catch (const exception &e) + { + cerr << "ERROR: during initialization: " << e.what() << endl; + rvapp->console()->processLastTextBuffer(); + exit( -1 ); + } + + // + // XXX dummyDev is leaking here. Best would be to pass it to the App so + // that it could delete it after startup, since it's no longer needed at + // that point. + // + + #ifdef PLATFORM_WINDOWS + TwkGLF::FBOVideoDevice* dummyDev = new TwkGLF::FBOVideoDevice(0, 10, 10, false); + IPCore::ImageRenderer::queryGL(); + const char* glVersion =(const char*)glGetString(GL_SHADING_LANGUAGE_VERSION); + IPCore::Shader::Function::useShadingLanguageVersion(glVersion); + #endif + + // + // Organize the input file list + // + + bool dorun = true; + + rvapp->createNewSessionFromFiles(opts.inputFiles); + + rvapp->processNetworkOpts(); + + int rval = 0; + + rvapp->console()->processTextBuffer(); + +#if !defined(PLATFORM_WINDOWS) + // + // Overwrite python's signal handler + // + + if (signal(SIGINT, control_c_handler) == SIG_ERR) + { + cout << "ERROR: failed to install SIGINT signal handler" << endl; + } +#endif + + TwkUtil::setThreadName("RV Main"); + + if (dorun) + { + try + { + rval = app->exec(); + } + catch (TwkExc::Exception& exc) + { + cerr << exc; + exit(-1); + } + catch (const exception &e) + { + cerr << "ERROR: Unhandled exception during execution: " + << e.what() + << endl; + + exit(-1); + } + catch (...) + { + cerr << "ERROR: Unhandled unknown exception" << endl; + exit(-1); + } + } + +#ifdef PTW32_STATIC_LIB + pthread_win32_thread_detach_np(); + pthread_win32_process_detach_np(); +#endif + +#ifdef PLATFORM_DARWIN + releasePool(); +#endif + + TwkFB::ThreadPool::shutdown(); + TwkMovie::GenericIO::shutdown(); // Shutdown TwkMovie::GenericIO plugins + TwkFB::GenericIO::shutdown(); // Shutdown TwkFB::GenericIO plugins + + TwkGLF::UninitPBOPools(); + + // Ensure to delete the QApplication before calling finalizePython + delete rvapp; + delete app; + TwkApp::finalizePython(); + + return rval; +} diff --git a/src/bin/apps/rv/qt.conf b/src/bin/apps/rv/qt.conf new file mode 100644 index 000000000..1fbc1a112 --- /dev/null +++ b/src/bin/apps/rv/qt.conf @@ -0,0 +1,5 @@ +[Paths] +Prefix = .. +Plugins = plugins/Qt +Qml2Imports = plugins/QML + diff --git a/src/bin/apps/rv/qt/etc/qt.conf b/src/bin/apps/rv/qt/etc/qt.conf new file mode 100644 index 000000000..45723ff62 --- /dev/null +++ b/src/bin/apps/rv/qt/etc/qt.conf @@ -0,0 +1,4 @@ +[Paths] +Prefix = .. +Plugins = plugins/Qt + diff --git a/src/bin/apps/rv/rv.qrc b/src/bin/apps/rv/rv.qrc new file mode 100644 index 000000000..c2b57480a --- /dev/null +++ b/src/bin/apps/rv/rv.qrc @@ -0,0 +1,6 @@ + + + + qt/etc/qt.conf + + \ No newline at end of file diff --git a/src/bin/apps/rv/rv.wrapper b/src/bin/apps/rv/rv.wrapper new file mode 100644 index 000000000..b390c3e4a --- /dev/null +++ b/src/bin/apps/rv/rv.wrapper @@ -0,0 +1,87 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the RV_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set RV_HOME if invoked from a link to the script +# and RV_HOME is not already set +# + +set noglob + +# +# Uncomment this if you want to use the pulseaudio/libsndfile package mismatch +# workaround below. +# +#setenv RV_USE_PULSEAUDIO_WORKAROUND 1 + +# +# Workaround for pulseaudio/libsndfile package mismatch on FC/Cent that causes +# crash when accessing preferences. +# +if ((-e /usr/bin/padsp) && $?RV_USE_PULSEAUDIO_WORKAROUND) then + setenv OPTIONAL_PADSP /usr/bin/padsp +else + setenv OPTIONAL_PADSP "" +endif + +if ($?QT_PLUGIN_PATH) then + echo "INFO: warning: QT_PLUGIN_PATH is set, which can cause RV to load the wrong Qt libraries/plugins. Unsetting..." + unsetenv QT_PLUGIN_PATH +endif + +# +# Uncomment this if you're on an older linux distro and RV is hanging in +# the Audio preferences or on startup with audio sources +# +#setenv PA_ALSA_EXCLUDE_DMIX_DEFAULT 1 + +# +# For unknown reasons, LANG causes problems when set to +# interesting values (like fr_FR.UTF-8). +# +unsetenv LANG + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +set platform = i386 +set rvbin = "$RV_HOME/bin/rv.bin.$platform" + +if (! (-e $rvbin) ) then + set rvbin = "$RV_HOME/bin/rv.bin" +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec RV + + +if ("x$1" == "x") then + # No parameter supplied, just execute app + exec $OPTIONAL_PADSP $rvbin $*:q +else + if ( "$1" == "-d" || "$1" == "--debug") then + shift # skip '-d / --debug' + gdb $OPTIONAL_PADSP $rvbin $*:q + else + exec $OPTIONAL_PADSP $rvbin $*:q + endif +endif diff --git a/src/bin/apps/rv/utf8WinMain.cpp b/src/bin/apps/rv/utf8WinMain.cpp new file mode 100644 index 000000000..5422e96df --- /dev/null +++ b/src/bin/apps/rv/utf8WinMain.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8WinMain.cpp" \ No newline at end of file diff --git a/src/bin/apps/rvpkg/CMakeLists.txt b/src/bin/apps/rvpkg/CMakeLists.txt new file mode 100644 index 000000000..c11176cd3 --- /dev/null +++ b/src/bin/apps/rvpkg/CMakeLists.txt @@ -0,0 +1,87 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "rvpkg" +) + +LIST(APPEND _sources main.cpp utf8Main.cpp) + +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +FIND_PACKAGE( + Qt5 + COMPONENTS Core + REQUIRED +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE TwkApp + TwkDeploy + TwkExc + TwkUtil + stl_ext + RvPackage + arg + stl_ext + Qt5::Core +) + +IF(RV_TARGET_DARWIN) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE DarwinBundle + ) +ELSE() + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE QTBundle + ) +ENDIF() + +IF(RV_TARGET_WINDOWS) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE win_posix win_pthreads + ) +ENDIF() + +IF(RV_TARGET_WINDOWS) + ADD_LINK_OPTIONS("-SUBSYSTEM:CONSOLE") +ENDIF() + +TARGET_COMPILE_OPTIONS( + ${_target} + PRIVATE -DGIT_HEAD=\"${RV_GIT_COMMIT_SHORT_HASH}\" -DRELEASE_DESCRIPTION=\"${RV_RELEASE_DESCRIPTION}\" +) + +RV_STAGE(TYPE "EXECUTABLE" TARGET ${_target}) + +ADD_CUSTOM_COMMAND( + COMMENT "Installing Open RV Packages" + OUTPUT ${RV_STAGE_PLUGINS_PACKAGES_DIR}/rvinstall ${INSTALLED_RV_PACKAGE_LIST} + COMMAND $ -force -install -add ${RV_STAGE_PLUGINS_DIR} ${RV_PACKAGE_LIST} + WORKING_DIRECTORY ${RV_STAGE_BIN_DIR} + DEPENDS ${_target} packages +) + +ADD_CUSTOM_TARGET( + rvinstall ALL + DEPENDS ${RV_STAGE_PLUGINS_PACKAGES_DIR}/rvinstall +) + +ADD_DEPENDENCIES(installed_packages rvinstall) diff --git a/src/bin/apps/rvpkg/main.cpp b/src/bin/apps/rvpkg/main.cpp new file mode 100644 index 000000000..b5aafd642 --- /dev/null +++ b/src/bin/apps/rvpkg/main.cpp @@ -0,0 +1,661 @@ +//****************************************************************************** +// Copyright (c) 2001-2005 Tweak Inc. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifdef _MSC_VER + // + // We are targetting at least XP, (IE no windows95, etc). + // + #define WINVER 0x0501 + #define _WIN32_WINNT 0x0501 + #include + #include + #include + #include + #include + // + // NOTE: win_pthreads, which supplies implement.h, seems + // targetted at an earlier version of windows (pre-XP). If you + // include implement.h here, it won't compile. But as far as I + // can tell, it's not needed, so just leave it out. + // + // #include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef PLATFORM_LINUX +#include +#include +#include +#endif + +#ifdef PLATFORM_DARWIN +#include +#else +#include +#endif + +#include +#include + +// +// Check that a version actually exists. If we're compiling opt error +// out in cpp. Otherwise, just note the lack of version info. +// + +#if MAJOR_VERSION == 99 +#if NDEBUG +//#error ********* NO VERSION INFORMATION *********** +#else +#ifndef _MSC_VER +#warning ********* NO VERSION INFORMATION *********** +#endif +#endif +#endif + +#ifdef WIN32 +#define FILE_SEP ";" +#else +#define FILE_SEP ":" +#endif + +using namespace std; +using namespace Rv; +using namespace TwkUtil; + +void setEnvVar(const string& var, const string& val) +{ +#ifdef WIN32 + ostringstream str; + str << var << "=" << val; + putenv(str.str().c_str()); +#else + setenv(var.c_str(), val.c_str(), 1); +#endif +} + +void setEnvVar(const string& var, const QFileInfo& val) +{ + setEnvVar(var, val.absoluteFilePath().toUtf8().data()); +} + +void addPathToEnv(const string& var, const string& path) +{ + string newValue; + + if (const char* v = getenv(var.c_str())) + { + string newValue = pathConform(v); + newValue += FILE_SEP; + } + + newValue += pathConform(path); + setEnvVar(var, newValue); +} + +bool hasPathInEnv(const string& var, const string& path0) +{ + string path = pathConform(path0); + + if (const char* v = getenv(var.c_str())) + { + vector tokens; + stl_ext::tokenize(tokens, pathConform(v), FILE_SEP); + + for (size_t i = 0; i < tokens.size(); i++) + { + if (tokens[i] == path) return true; + } + } + + return false; +} + +void includeIfNotThere(const string& path0) +{ + string path = pathConform(path0); + + if (!hasPathInEnv("RV_SUPPORT_PATH", path)) + { + addPathToEnv("RV_SUPPORT_PATH", path); + } +} + + +vector inputArgs; + +int +parseInFiles(int argc, char *argv[]) +{ + for (int i=0; i& outindexlist) +{ + set indexSet; + + for (size_t i = 0; i < inputArgs.size(); i++) + { + for (size_t q = 0; q < inlist.size(); q++) + { + if (matchesNameOrFile(inlist[q], inputArgs[i])) + { + indexSet.insert(q); + } + } + } + + for (set::iterator i = indexSet.begin(); i != indexSet.end(); ++i) + { + outindexlist.push_back(*i); + } +} + +int +utf8Main(int argc, char *argv[]) +{ + QCoreApplication app(argc, argv); + + // + // Parse cmd line args + // + + int install = 0; + int uninstall = 0; + int list = 0; + int info = 0; + int add = 0; + char* addDir = 0; + int remove = 0; + int env = 0; + int force = 0; + int optin = 0; + char* filename = 0; + char* withDir = 0; + char* onlyDir = 0; + + // + // We handle the '--help' flag by changing it to '-help' for + // argparse to work. + // + for (int i = 0; i < argc; ++i) + { + if (!strcmp("--help", argv[i])) + { + strcpy(argv[i], "-help"); + break; + } + } + + if (arg_parse + (argc, argv, + "", "Usage: rvpkg command line .rvpkg management", + "", " In examples below 'area' means a support area", + "", " Many commands can use one of: package name, rvpkg file name,", + "", " or full path to rvpkg file. the commands -remove, -info,", + "", " -install, -uninstall, operating on already added packages.", + "", " They will not work on packages not visible to -list", + "", "", + "", " List all in RV_SUPPORT_PATH: rvpkg -list", + "", " List in specific area: rvpkg -list -only /path/to/support/area", + "", " List including an area: rvpkg -list -include /path/to/additional/support/area", + "", " Info about a package: rvpkg -info /path/to/file.rvpkg", + "", " Add package(s) to an area: rvpkg -add /path/to/area /path/to/file1.rvpkg /path/to/file2.rvpkg ...", + "", " Remove package(s): rvpkg -remove /path/to/file1.rvpkg ...", + "", " Install package(s): rvpkg -install /path/to/file1.rvpkg ...", + "", " Uninstall package(s): rvpkg -uninstall /path/to/file1.rvpkg ...", + "", " Add and install package(s): rvpkg -install -add /path/to/area /path/to/file1.rvpkg ...", + "", " Opt-in all users to optional package(s): rvpkg -optin /path/to/file1.rvpkg ...", + "", "", + "", ARG_SUBR(parseInFiles), "Input packages names or rvpkg files", + "-include %S", &withDir, "include directory as if part of RV_SUPPORT_PATH", + "-env", ARG_FLAG(&env), "show RV_SUPPORT_PATH include app areas", + "-only %S", &onlyDir, "use directory as sole content of RV_SUPPORT_PATH", + "-add %S", &addDir, "add packages to specified support directory", + "-remove", ARG_FLAG(&remove), "remove packages (by name, rvpkg name, or full path to rvpkg)", + "-install", ARG_FLAG(&install), "install packages (by name, rvpkg name, or full path to rvpkg)", + "-uninstall", ARG_FLAG(&uninstall), "uninstall packages (by name, rvpkg name, or full path to rvpkg)", + "-optin", ARG_FLAG(&optin), "make installed optional packages opt-in by default for all users", + "-list", ARG_FLAG(&list), "list installed packages", + "-info", ARG_FLAG(&info), "detailed info about packages (by name, rvpkg name, or full path to rvpkg)", + "-force", ARG_FLAG(&force), "Assume answer is 'y' to any confirmations -- don't be interactive", + NULL) < 0) + { + exit(-1); + } + + add = addDir ? 1 : 0; + + if (inputArgs.empty() && !list && !info && !env) + { + cout << "ERROR: use -help for usage" << endl; + } + + if (add && remove) + { + cout << "ERROR: only one of -add or -remove allowed" << endl; + exit(-1); + } + + if (onlyDir && (withDir || addDir)) + { + cout << "ERROR: -only cannot be used with -include or -add" << endl; + exit(-1); + } + + if (addDir && withDir) + { + cout << "WARNING: -include ignored with -add" << endl; + } + + if ((remove && install) || (add && uninstall)) + { + cout << "ERROR: conflicting arguments" << endl; + exit(-1); + } + + if ((remove || add || install || uninstall) && (list || info)) + { + cout << "ERROR: -list and -info do not work with other arguments" << endl; + exit(-1); + } + + if (list && info) + { + cout << "ERROR: only one of -list or -info allowed" << endl; + exit(-1); + } + + if ((info || install || uninstall || remove || add) && inputArgs.empty()) + { + cout << "ERROR: need more arguments" << endl; + exit(-1); + } + +#ifdef PLATFORM_WINDOWS + string addDirConformed; + string withDirConformed; + string onlyDirConformed; + + if (addDir) + { + addDirConformed = pathConform(addDir); + addDir = (char*)addDirConformed.c_str(); + } + + if (withDir) + { + withDirConformed = pathConform(withDir); + withDir = (char*)withDirConformed.c_str(); + } + + if (onlyDir) + { + onlyDirConformed = pathConform(onlyDir); + onlyDir = (char*)onlyDirConformed.c_str(); + } +#endif + + if (addDir) includeIfNotThere(addDir); + else if (withDir) includeIfNotThere(withDir); + else if (onlyDir) setEnvVar("RV_SUPPORT_PATH", onlyDir); + + BundleType bundle(APPNAME, MAJOR_VERSION, MINOR_VERSION, REVISION_NUMBER); + + setEnvVar("LANG", "C"); + setEnvVar("LC_ALL", "C"); + + if (env) + { + TwkApp::Bundle::PathVector paths = bundle.pluginPath("Packages"); + + for (size_t i = 0; i < paths.size(); i++) + { + cout << paths[i] << endl; + } + + exit(0); + } + + TWK_DEPLOY_APP_OBJECT dobj(MAJOR_VERSION, + MINOR_VERSION, + REVISION_NUMBER, + argc, argv, + RELEASE_DESCRIPTION, + "HEAD=" GIT_HEAD); + + Rv::PackageManager manager; + manager.loadPackages(); + PackageManager::PackageList& packages = manager.packageList(); + + if (force) manager.setNoConfirmation(); + + if (list) + { + if (packages.empty()) + { + cout << "No packages found" << endl; + } + + for (size_t i = 0; i < packages.size(); i++) + { + PackageManager::Package& p = packages[i]; + cout << (p.installed ? "I" : "-"); + cout << " " << (p.loadable ? "L" : "-"); + cout << " " << (p.optional ? "O" : "-"); + + cout << " " << p.version + << " \"" << p.name + << "\" " << p.file + << endl; + } + } + + if (info) + { + vector indices; + matchingPackages(packages, indices); + + if (indices.empty()) + { + cout << "No matching packages found" << endl; + } + + for (size_t i = 0; i < indices.size(); i++) + { + PackageManager::Package& p = packages[indices[i]]; + + cout << "Name: " << p.name << endl; + cout << "Version: " << p.version << endl; + cout << "Installed: " << (p.installed ? "YES" : "NO") << endl; + cout << "Loadable: " << (p.loadable ? "YES" : "NO") << endl; + cout << "Directory: " << p.dir << endl; + cout << "Author: " << p.author << endl; + cout << "Organization: " << p.organization << endl; + cout << "Contact: " << p.contact << endl; + cout << "URL: " << p.url << endl; + cout << "Requires: " << p.requires << endl; + cout << "RV-Version: " << p.rvversion << endl; + cout << "OpenRV-Version: " << p.openrvversion << endl; + cout << "Hidden: " << (p.hidden ? "YES" : "NO") << endl; + cout << "System: " << (p.system ? "YES" : "NO") << endl; + cout << "Optional: " << (p.optional ? "YES" : "NO") << endl; + cout << "Writable: " << (p.fileWritable ? "YES" : "NO") << endl; + cout << "Dir-Writable: " << (p.dirWritable ? "YES" : "NO") << endl; + + cout << "Modes:"; + + for (size_t j = 0; j < p.modes.size(); j++) + { + cout << " " << p.modes[j].file; + } + + cout << endl; + + cout << "Files:"; + + for (size_t j = 0; j < p.files.size(); j++) + { + cout << " " << p.files[j]; + } + + cout << endl; + } + } + + if (optin) + { + vector indices; + matchingPackages(packages, indices); + + if (indices.empty()) + { + cout << "No matching packages found" << endl; + } + + for (size_t i = 0; i < indices.size(); i++) + { + PackageManager::Package& p = packages[indices[i]]; + + if (!p.optional) + { + cout << "ERROR: " << p.file.toUtf8().constData() + << " is not an optional package -- ignoring" + << endl; + continue; + } + + QString file = p.file; + QFileInfo info(file); + + if (!info.exists()) + { + cout << "ERROR: " << p.file.toUtf8().constData() << " doesn't exist -- ignoring" << endl; + continue; + } + + QDir dir = info.absoluteDir(); + dir.cdUp(); + dir.cd("Mu"); + QString rvload2 = dir.absoluteFilePath("rvload2"); + QFileInfo rvloadInfo(rvload2); + + if (!rvloadInfo.exists()) + { + cout << "ERROR: missing rvload2 file at " + << rvload2.toUtf8().constData() + << " -- ignoring" + << endl; + break; + } + + PackageManager::ModeEntryList entries = manager.loadModeFile(rvload2); + + // + // Just force the package to become not optional + // + + QString basename = info.fileName(); + cout << "INFO: for package " << basename.toUtf8().constData() << endl; + + for (size_t i = 0; i < entries.size(); i++) + { + PackageManager::ModeEntry& entry = entries[i]; + + if (entry.package == basename) + { + entry.loaded = true; + entry.optional = false; + + cout << "INFO: opting-in all users for mode " + << entry.name.toUtf8().constData() + << endl; + } + } + + manager.writeModeFile(rvload2, entries); + } + } + + if (add) + { + QStringList files; + + for (size_t i = 0; i < inputArgs.size(); i++) + { + files.push_back(inputArgs[i].c_str()); + } + + if (!manager.addPackages(files, addDir)) + { + cout << "exiting" << endl; + exit(-1); + } + + // + // Convert the input args to existing pkg file names so that + // -install can operate on them too + // + + for (size_t i = 0; i < inputArgs.size(); i++) + { + QString name(inputArgs[i].c_str()); + QString base = name.split("/").back(); + QDir dir(addDir); + dir.cd("Packages"); + if (!dir.exists()) exit(-1); + + QString path = dir.absoluteFilePath(base); + inputArgs[i] = path.toUtf8().constData(); + cout << inputArgs[i] << endl; + } + } + + + if (install) + { + vector indices; + matchingPackages(packages, indices); + + if (indices.empty()) + { + cout << "No matching packages found" << endl; + } + + for (size_t i = 0; i < indices.size(); i++) + { + PackageManager::Package& p = packages[indices[i]]; + + if (p.installed) + { + cout << "WARNING: " << p.name << " is already installed" << endl; + } + else + { + cout << "INFO: installing " << p.file << endl; + + if (!manager.installPackage(p)) + { + cout << "ERROR: failed to install " << p.file << endl; + } + } + } + } + + if (uninstall) + { + vector indices; + matchingPackages(packages, indices); + + if (indices.empty()) + { + cout << "No matching packages found" << endl; + } + + for (size_t i = 0; i < indices.size(); i++) + { + PackageManager::Package& p = packages[indices[i]]; + + if (p.installed) + { + manager.uninstallPackage(p); + } + else + { + cout << "INFO: " << p.name << " is not installed" << endl; + } + } + } + + if (remove) + { + vector indices; + matchingPackages(packages, indices); + QStringList files; + + if (indices.empty()) + { + cout << "No matching packages found" << endl; + } + + for (size_t i = 0; i < indices.size(); i++) + { + PackageManager::Package& p = packages[indices[i]]; + files.push_back(p.file); + } + + manager.removePackages(files); + } + + return 0; +} diff --git a/src/bin/apps/rvpkg/rvpkg.wrapper b/src/bin/apps/rvpkg/rvpkg.wrapper new file mode 100644 index 000000000..07a1d654f --- /dev/null +++ b/src/bin/apps/rvpkg/rvpkg.wrapper @@ -0,0 +1,55 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the RV_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set RV_HOME if invoked from a link to the script +# and RV_HOME is not already set +# + +set noglob + +# +# Uncomment this if you're on an older linux distro and RV is hanging in +# the Audio preferences or on startup with audio sources +# +#setenv PA_ALSA_EXCLUDE_DMIX_DEFAULT 1 + +# +# For unknown reasons, LANG causes problems when set to +# interesting values (like fr_FR.UTF-8). +# +unsetenv LANG + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +set platform = i386 +set rvpkgbin = "$RV_HOME/bin/rvpkg.bin.$platform" + +if (! (-e $rvpkgbin) ) then + set rvpkgbin = "$RV_HOME/bin/rvpkg.bin" +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec RV + +exec $rvpkgbin $*:q diff --git a/src/bin/apps/rvpkg/utf8Main.cpp b/src/bin/apps/rvpkg/utf8Main.cpp new file mode 100644 index 000000000..69e097cc9 --- /dev/null +++ b/src/bin/apps/rvpkg/utf8Main.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.cpp" \ No newline at end of file diff --git a/src/bin/apps/rvprof/CMakeLists.txt b/src/bin/apps/rvprof/CMakeLists.txt new file mode 100644 index 000000000..210c8ea00 --- /dev/null +++ b/src/bin/apps/rvprof/CMakeLists.txt @@ -0,0 +1,54 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "rvprof" +) + +SET(CMAKE_AUTOUIC + ON +) +SET(CMAKE_AUTOMOC + ON +) +SET(CMAKE_AUTORCC + ON +) + +# Add main sources +SET(_sources + main.cpp utf8Main.cpp +) + +# Add QT-specific source files +LIST(APPEND _sources VisMainWindow.cpp) + +# The original Makefile was flagging this executable as NSAPP but it wasn't distributed as such, updating to simple executable. +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +FIND_PACKAGE( + Qt5 + COMPONENTS Core Gui OpenGL Widgets + REQUIRED +) +FIND_PACKAGE(OpenGL REQUIRED) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE TwkMath TwkGLText OpenGL::GLU Qt5::Gui Qt5::OpenGL Qt5::Widgets +) + +RV_STAGE(TYPE "EXECUTABLE" TARGET ${_target}) diff --git a/src/bin/apps/rvprof/DockWidget.ui b/src/bin/apps/rvprof/DockWidget.ui new file mode 100644 index 000000000..1cd126b2e --- /dev/null +++ b/src/bin/apps/rvprof/DockWidget.ui @@ -0,0 +1,411 @@ + + + DockWidget + + + + 0 + 0 + 1328 + 648 + + + + QDockWidget::DockWidgetFeatureMask + + + Qt::BottomDockWidgetArea|Qt::TopDockWidgetArea + + + Statistics + + + + + -1 + + + 0 + + + 4 + + + 0 + + + 4 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 6 + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 6 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 100 + 16777215 + + + + Start Frame: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + + + + + + 100 + 16777215 + + + + End Frame: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + + + + + Total Time: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + -- + + + + + + + + + QFormLayout::FieldsStayAtSizeHint + + + + + Start Time + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + + + + + End Time + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + QFormLayout::FieldsStayAtSizeHint + + + + + RMS Computed Refresh Rate: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + --Hz + + + + + + + Min Refresh Rate + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + --Hz + + + + + + + Max Refresh Rate + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + --Hz + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + + + Computed FPS: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + -- + + + + + + + Actual FPS: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 60 + 0 + + + + + 60 + 16777215 + + + + + + + + Show Ideal Frames + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + Show Eval Timing + + + true + + + + + + + Qt::Horizontal + + + + 193 + 20 + + + + + + + + + + + + + + + + + + + diff --git a/src/bin/apps/rvprof/FileViewDialog.ui b/src/bin/apps/rvprof/FileViewDialog.ui new file mode 100644 index 000000000..3d42c8940 --- /dev/null +++ b/src/bin/apps/rvprof/FileViewDialog.ui @@ -0,0 +1,37 @@ + + + FileViewDialog + + + + 0 + 0 + 941 + 658 + + + + Raw File + + + + 0 + + + 0 + + + + + + Courier + 14 + + + + + + + + + diff --git a/src/bin/apps/rvprof/Info.plist b/src/bin/apps/rvprof/Info.plist new file mode 100644 index 000000000..1b8957627 --- /dev/null +++ b/src/bin/apps/rvprof/Info.plist @@ -0,0 +1,52 @@ + + + + + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + rvprof + timedata + + CFBundleTypeIconFile + rvprof.icns + CFBundleTypeName + playbackVis file + CFBundleTypeOSTypes + + RVPROF + + LSIsAppleDefaultForType + Yes + + + CFBundleExecutable + rvprof + CFBundleIconFile + rvprof.icns + CFBundleIdentifier + com.tweakfilms.rvprof.0.1 + CFBundleName + rvprof + CFBundlePackageType + APPL + CFBundleResourcesFileMapped + Yes + CFBundleShortVersionString + 0.1.0 + CFBundleVersion + 0.1 + LSHasLocalizedDisplayName + 0 + NSAppleScriptEnabled + 0 + NSHumanReadableCopyright + Copyright (c) Autodesk, Inc. All rights reserved. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/src/bin/apps/rvprof/VisMainWindow.cpp b/src/bin/apps/rvprof/VisMainWindow.cpp new file mode 100644 index 000000000..2946e9e89 --- /dev/null +++ b/src/bin/apps/rvprof/VisMainWindow.cpp @@ -0,0 +1,1585 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "VisMainWindow.h" +#include +#include +#include +#ifdef PLATFORM_DARWIN + #include +#else + #include +#endif + +using namespace TwkGLText; + +inline void glVertex( const TwkMath::Vec3f &v ) +{ + glVertex3fv( ( GLfloat * )( &v ) ); +} + +inline void glVertex( const TwkMath::Vec4f &v ) +{ + glVertex4fv( ( GLfloat * )( &v ) ); +} + +inline TwkMath::Mat44f getMatrix( GLenum matrix ) +{ + GLfloat m[16]; + glGetFloatv( matrix, m ); + + // Unfortunately, open GL stores matrices in COLUMN-major + // format, whereas we store them in the much more sane ROW-major + // format. + return TwkMath::Mat44f( m[0], m[4], m[8], m[12], + m[1], m[5], m[9], m[13], + m[2], m[6], m[10], m[14], + m[3], m[7], m[11], m[15] ); +} + +inline void glLoadMatrix( const TwkMath::Mat44f &m ) +{ + GLfloat mv[16]; + + mv[0] = m[0][0]; + mv[1] = m[1][0]; + mv[2] = m[2][0]; + mv[3] = m[3][0]; + + mv[4] = m[0][1]; + mv[5] = m[1][1]; + mv[6] = m[2][1]; + mv[7] = m[3][1]; + + mv[8] = m[0][2]; + mv[9] = m[1][2]; + mv[10] = m[2][2]; + mv[11] = m[3][2]; + + mv[12] = m[0][3]; + mv[13] = m[1][3]; + mv[14] = m[2][3]; + mv[15] = m[3][3]; + + glLoadMatrixf( mv ); +} + +inline void glMultMatrix( const TwkMath::Mat44f &m ) +{ + GLfloat mv[16]; + + mv[0] = m[0][0]; + mv[1] = m[1][0]; + mv[2] = m[2][0]; + mv[3] = m[3][0]; + + mv[4] = m[0][1]; + mv[5] = m[1][1]; + mv[6] = m[2][1]; + mv[7] = m[3][1]; + + mv[8] = m[0][2]; + mv[9] = m[1][2]; + mv[10] = m[2][2]; + mv[11] = m[3][2]; + + mv[12] = m[0][3]; + mv[13] = m[1][3]; + mv[14] = m[2][3]; + mv[15] = m[3][3]; + + glMultMatrixf( mv ); +} + +using namespace std; +using namespace TwkMath; + +//---------------------------------------------------------------------- + +VisMainWindow::VisMainWindow(QWidget *parent) : + m_mouseTime(0.0), + QMainWindow(parent) +{ + m_ui.setupUi(this); + + m_dockWidget = new QDockWidget(this); + m_dockUI.setupUi(m_dockWidget); + addDockWidget(Qt::BottomDockWidgetArea, m_dockWidget); + m_dockWidget->show(); + + m_fileViewDialog = new QDialog(NULL); + m_fileViewUI.setupUi(m_fileViewDialog); + + connect(m_ui.actionOpen, SIGNAL(triggered()), this, SLOT(openFile())); + connect(m_ui.actionQuit, SIGNAL(triggered()), this, SLOT(quit())); + connect(m_ui.actionShow_Raw_Profile_Data, SIGNAL(triggered()), this, SLOT(showFile())); + connect(m_dockUI.startEdit, SIGNAL(editingFinished()), this, SLOT(rangeChanged())); + connect(m_dockUI.endEdit, SIGNAL(editingFinished()), this, SLOT(rangeChanged())); + + connect(m_dockUI.startTimeEdit, SIGNAL(editingFinished()), this, SLOT(rangeChanged())); + connect(m_dockUI.endTimeEdit, SIGNAL(editingFinished()), this, SLOT(rangeChanged())); + + connect(m_dockUI.showIdealFramesButton, SIGNAL(stateChanged(int)), + this, SLOT(showIdealFrames(int))); + connect(m_dockUI.showEvalTimingButton, SIGNAL(stateChanged(int)), + this, SLOT(showEvalTiming(int))); + connect(m_dockUI.actualFPSEdit, SIGNAL(editingFinished()), this, SLOT(fpsChanged())); + + m_glWidget = new GLView(this, m_dockUI.readoutEdit, this); + setCentralWidget(m_glWidget); + m_glWidget->show(); + + qApp->installEventFilter(this); +} + +bool +VisMainWindow::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::FileOpen) + { + QFileOpenEvent* fileEvent = static_cast(event); + QString filename = fileEvent->file().toUtf8().data(); + + if (filename == "") + { + filename = fileEvent->url().path(); + } + + readFile(filename); + return true; + } + + return QMainWindow::eventFilter(obj, event); +} + +void +VisMainWindow::openFile() +{ + QString filename = + QFileDialog::getOpenFileName(this, "Select File", ".", "Open RV Profile Data (*.rvprof *.timedata)"); + + readFile(filename); +} + +void +VisMainWindow::readFile(const QString& filename) +{ + QFile infile(filename); + + m_fileViewUI.plainTextEdit->clear(); + + if (!infile.exists()) + { + cerr << "ERROR: file doesn't exist: " << filename.toUtf8().constData() << endl; + return; + } + + if (!infile.open(QIODevice::ReadOnly | QIODevice::Text)) + { + cerr << "ERROR: can't open: " << filename.toUtf8().constData() << endl; + return; + } + + QFileInfo info(filename); + setWindowTitle(QString("Open RV Profile Viewer -- %1").arg(info.baseName())); + + DataVector data; + + QString fileContents; + + while (!infile.atEnd()) + { + QByteArray array = infile.readLine(); + QString line(array); + fileContents += line; + if (line.startsWith("#") || line == "") continue; + QStringList parts = line.split(","); + + if (line.contains("=")) + { + // + // NEW FORMAT + // + + DataElement e; +#define OFFSET_PAIR(NAME,FIELD) \ + {#NAME "0", ((char*)&e.FIELD ## 0) - ((char*)&e)},\ + {#NAME "1", ((char*)&e.FIELD ## 1) - ((char*)&e)} + +#define OFFSET_SINGLE(NAME,FIELD) \ + {#NAME, ((char*)&e.FIELD) - ((char*)&e)} + + NameOffsetPair offsets[] = { + OFFSET_PAIR(R,render), + OFFSET_PAIR(S,swap), + OFFSET_PAIR(E,eval), + OFFSET_PAIR(U,userRender), + OFFSET_PAIR(FC,frameChange), + OFFSET_PAIR(IR,internalRender), + OFFSET_PAIR(PR,prefetch), + OFFSET_PAIR(CT,cacheTest), + OFFSET_PAIR(EI,evalGraph), + OFFSET_PAIR(ID,evalID), + OFFSET_PAIR(CQ,cacheQuery), + OFFSET_PAIR(CE,cacheEval), + OFFSET_PAIR(IO,io), + OFFSET_PAIR(THA,restartA), + OFFSET_PAIR(THB,restartB), + OFFSET_PAIR(CTL,cacheTestLock), + OFFSET_PAIR(DSDP,setDisplayFrame), + OFFSET_PAIR(FCT,frameCachedTest), + OFFSET_PAIR(WAK,awaken), + OFFSET_PAIR(PRR,prefetchRender), + {0, 0} + }; + + memset(&e, 0, sizeof(DataElement)); + + for (size_t i = 0; i < parts.size(); i++) + { + QStringList nameValue = parts[i].split("="); + + if (nameValue.size() != 2) + { + cout << "ERROR: reading file " << filename.toUtf8().constData() << endl; + cout << "ERROR: reading this field: \"" << parts[i].toUtf8().constData() << "\"" << endl; + continue; + } + + for (const NameOffsetPair* p = offsets; p->name; p++) + { + if (nameValue[0] == p->name) + { + *(double*)((char*)&e + p->offset) = nameValue[1].toDouble(); + break; + } + } + + if (nameValue[0] == "GC") e.gccount = nameValue[1].toInt(); + else if (nameValue[0] == "F") e.frame = nameValue[1].toInt(); + else if (nameValue[0] == "EST") e.expectedSyncTime = nameValue[1].toFloat(); + else if (nameValue[0] == "DCO") e.deviceClockOffset = nameValue[1].toFloat(); + else if (nameValue[0] == "PRUP") e.prefetchUploadPlane = nameValue[1].toFloat(); + else if (nameValue[0] == "RRUP") e.renderUploadPlane = nameValue[1].toFloat(); + else if (nameValue[0] == "RFW") e.renderFenceWait = nameValue[1].toFloat(); + } + + data.push_back(e); + } + else + { + // + // OLD FORMAT(S) + // + + if (parts.size() > 12) + { + DataElement e; + size_t index = 0; + + e.render0 = parts[index++].toDouble(); + e.render1 = parts[index++].toDouble(); + e.swap0 = parts[index++].toDouble(); + e.swap1 = parts[index++].toDouble(); + e.eval0 = parts[index++].toDouble(); + e.eval1 = parts[index++].toDouble(); + e.userRender0 = parts[index++].toDouble(); + e.userRender1 = parts[index++].toDouble(); + e.frameChange0 = parts[index++].toDouble(); + e.frameChange1 = parts[index++].toDouble(); + e.internalRender0 = parts[index++].toDouble(); + e.internalRender1 = parts[index++].toDouble(); + e.prefetch0 = parts[index++].toDouble(); + e.prefetch1 = parts[index++].toDouble(); + + if (parts.size() > 22) + { + e.cacheTest0 = parts[index++].toDouble(); + e.cacheTest1 = parts[index++].toDouble(); + e.evalGraph0 = parts[index++].toDouble(); + e.evalGraph1 = parts[index++].toDouble(); + } + else + { + e.cacheTest0 = 0; + e.cacheTest1 = 0; + e.evalGraph0 = 0; + e.evalGraph1 = 0; + } + + if (parts.size() > 16) + { + e.evalID0 = parts[index++].toDouble(); + e.evalID1 = parts[index++].toDouble(); + e.cacheQuery0 = parts[index++].toDouble(); + e.cacheQuery1 = parts[index++].toDouble(); + e.cacheEval0 = parts[index++].toDouble(); + e.cacheEval1 = parts[index++].toDouble(); + + if (parts.size() >= 28) + { + e.io0 = parts[index++].toDouble(); + e.io1 = parts[index++].toDouble(); + } + } + else + { + e.evalID0 = 0; + e.evalID1 = 0; + e.cacheQuery0 = 0; + e.cacheQuery1 = 0; + e.cacheEval0 = 0; + e.cacheEval1 = 0; + } + + e.gccount = parts[index++].toInt(); + e.frame = parts[index++].toInt(); + + data.push_back(e); + } + } + } + + cout << "INFO: found " << data.size() << " elements" << endl; + m_glWidget->setData(data); + m_fileViewUI.plainTextEdit->setPlainText(fileContents); +} + +void +VisMainWindow::showFile() +{ + if (m_fileViewDialog->isVisible()) m_fileViewDialog->hide(); + else m_fileViewDialog->show(); +} + +void +VisMainWindow::quit() +{ + qApp->quit(); +} + +void +VisMainWindow::showIdealFrames(int b) +{ + m_glWidget->setShowIdealFrames(b == Qt::Checked); +} + +void +VisMainWindow::showEvalTiming(int b) +{ + m_glWidget->setShowEvalTiming(b == Qt::Checked); +} + +void +VisMainWindow::fpsChanged() +{ + m_glWidget->setActualFPS(m_dockUI.actualFPSEdit->text().toFloat()); +} + +void +VisMainWindow::updateTimeRangeFromGLView() +{ + m_dockUI.startTimeEdit->setText(QString("%1").arg(m_glWidget->rangeStart())); + m_dockUI.endTimeEdit-> setText(QString("%1").arg(m_glWidget->rangeEnd())); +} + +void +VisMainWindow::updateActualFPSFromGLView() +{ + m_dockUI.actualFPSEdit->setText(QString("%1").arg(m_glWidget->actualFPS())); +} + +void +VisMainWindow::setShowIdealFrames() +{ + m_dockUI.showIdealFramesButton->setCheckState(Qt::Checked); +} + +void +VisMainWindow::rangeChanged() +{ + float startTime = m_dockUI.startTimeEdit->text().toFloat(); + float endTime = m_dockUI.endTimeEdit->text().toFloat(); + + if (startTime < endTime) + { + m_glWidget->setTimeRange(startTime, endTime); + } + else + { + int start = m_dockUI.startEdit->text().toInt(); + int end = m_dockUI.endEdit->text().toInt(); + + if (end < start) + { + end = start; + m_dockUI.endEdit->setText(QString("%1").arg(end)); + } + + m_glWidget->setRange(start, end); + } + + const DataVector& data = m_glWidget->data(); + + size_t i0 = m_glWidget->startIndex(); + size_t i1 = m_glWidget->endIndex(); + + float minRR = 1000000; + float maxRR = 0; + double rr = 0; + size_t count = 0; + + if (i0 < data.size() && i1 < data.size()) + { + double startTime = data[i0].swap1; + double endTime = data[i1-1].swap1; + int frames = data[i1].frame - data[i0].frame + 1; + + float fps = float(frames) / (endTime - startTime); + m_glWidget->setComputedFPS(fps); + m_dockUI.fpsLabel->setText(QString("%1").arg(fps)); + + m_dockUI.totalTimeLabel->setText(QString("%1 sec").arg(endTime-startTime)); + + for (size_t i = i0; i <= i1; i++) + { + if (i > 0) + { + float d = data[i].swap1 - data[i-1].swap1; + rr += d * d; + count++; + minRR = std::min(d, minRR); + maxRR = std::max(d, maxRR); + } + } + + if (rr > 0) + { + rr = 1.0 / sqrt(rr / double(count)); + m_dockUI.refreshRateLabel->setText(QString("%1 Hz").arg(rr)); + m_glWidget->setComputedRefresh(rr); + } + else + { + m_glWidget->setComputedRefresh(0); + } + + // yes, this is right, its inverted so min is max + if (minRR > 0) m_dockUI.maxVariationLabel->setText(QString("%1 Hz").arg(1.0/ minRR)); + if (maxRR > 0) m_dockUI.minVariationLabel->setText(QString("%1 Hz").arg(1.0/ maxRR)); + } +} + +//---------------------------------------------------------------------- + +GLView::GLView(QWidget* parent, QTextEdit* readout, VisMainWindow* visWin) + : QGLWidget(QGLFormat(QGL::DoubleBuffer|QGL::SampleBuffers|QGL::Rgba), parent, NULL, Qt::Widget), + m_scale(1.0), + m_xtran(0), + m_ytran(0), + m_rangeStart(0), + m_rangeEnd(0), + m_rangeStartIndex(size_t(-1)), + m_rangeEndIndex(size_t(-1)), + m_actualFPS(0.0), + m_showIdealFrames(false), + m_showEvalTiming(true), + m_readoutSample(-1), + m_readout(readout), + m_computedRefresh(0.0), + m_computedFPS(0.0), + m_visWin(visWin) +{ + m_matrix.makeIdentity(); + setMouseTracking(true); +} + +void +GLView::setRange(int start, int end) +{ + bool foundStart = false; + bool foundEnd = false; + + for (size_t i = 0; i < m_data.size(); i++) + { + if (m_data[i].frame == start && !foundStart) + { + m_rangeStart = m_data[i].render0; + m_rangeStartIndex = i; + foundStart = true; + } + + if (m_data[i].frame == end+1 && !foundEnd) + { + if (i > 0) + { + m_rangeEnd = m_data[i].swap1; + m_rangeEndIndex = i; + foundEnd = true; + } + } + } + + updateGL(); +} + +void +GLView::setTimeRange(float start, float end) +{ + bool foundStart = false; + bool foundEnd = false; + + for (size_t i = 0; i < m_data.size(); i++) + { + if (m_data[i].render0 >= start && !foundStart) + { + m_rangeStart = m_data[i].render0; + m_rangeStartIndex = i; + foundStart = true; + } + + if (m_data[i].render0 >= end && !foundEnd) + { + if (i > 0) + { + m_rangeEnd = m_data[i].swap1; + m_rangeEndIndex = i; + foundEnd = true; + } + } + } + // cerr << "rangeEnd " << m_rangeEnd << " index " << m_rangeEndIndex << endl; + updateGL(); +} + +void +GLView::setData(const DataVector& data) +{ + m_data = data; + m_startTime = m_data.front().render0; + m_endTime = m_data.back().swap1; + + for (size_t i = 0; i < m_data.size(); i++) + { + DataElement& e = m_data[i]; + + if (i == 0) + { + e.repeatframe = false; + e.collectionOccured = false; + } + else + { + DataElement& e0 = m_data[i-1]; + e.repeatframe = e.frame == e0.frame; + e.collectionOccured = e.gccount != e0.gccount; + e0.singleFrame = !e0.repeatframe && !e.repeatframe; + } + } + + /* + float d = (m_endTime - m_startTime); + float y = d / float(width()); + m_matrix = scaleMatrix(Vec(d, d, 1)) + * translationMatrix(Vec(-m_startTime + 0.02, y * 0.25, 0)); + */ + float d = 1.0; + float y = 4.0 * (m_endTime - m_startTime)/height(); + m_matrix = scaleMatrix(Vec(d, d, 1)) + * translationMatrix(Vec(-m_startTime , y, 0)); + + updateGL(); +} + +void +GLView::rebuildIdealFrames() +{ + if (m_actualFPS < 0) return; +} + +void +GLView::setActualFPS(float fps) +{ + m_actualFPS = fps; + updateGL(); +} + +void +GLView::setShowEvalTiming(bool b) +{ + m_showEvalTiming = b; + updateGL(); +} + +void +GLView::setShowIdealFrames(bool b) +{ + m_showIdealFrames = b; + updateGL(); +} + +void +GLView::setComputedRefresh(float rate) +{ + m_computedRefresh = 1.0 / rate; + updateGL(); +} + +void +GLView::setComputedFPS(float fps) +{ + m_computedFPS = fps; + updateGL(); +} + +int +GLView::timeToSample (float t) +{ + for (size_t i = 0; i < m_data.size(); i++) + { + const DataElement& e = m_data[i]; + + if ((e.prefetch1 != 0.0 && e.prefetch1 >= t) || + (e.prefetch1 == 0.0 && e.swap1 >= t)) + { + return i; + } + } + return -1; +} + +#define APP0(str) m_readoutHtml.append(QString( str )) +#define APP1(str,a1) m_readoutHtml.append(QString( str ).arg(a1)) +#define APP2(str,a1,a2) m_readoutHtml.append(QString( str ).arg(a1).arg(a2)) +#define APP3(str,a1,a2,a3) m_readoutHtml.append(QString( str ).arg(a1).arg(a2).arg(a3)) +#define APPBR() m_readoutHtml.append(QString("\n
\n")) +#define APPCOLSTART(r,g,b) m_readoutHtml.append(QString("%1").arg(indent).arg(int(r*255),2,16,QChar('0')).arg(int(g*255),2,16,QChar('0')).arg(int(b*255),2,16,QChar('0'))) +#define APPCOLEND() m_readoutHtml.append(QString("")) +#define APPCOLTXT(r,g,b,s) m_readoutHtml.append(QString("%1♦♦♦♦ %5").arg(indent).arg(int(r*255),2,16,QChar('0')).arg(int(g*255),2,16,QChar('0')).arg(int(b*255),2,16,QChar('0')).arg(s)) + +#define APPITEM(str,a1) APP3(QString(str) + QString(", start %1, end %2, total %3
\n"), a1 ## 0, a1 ## 1, a1 ## 1 - a1 ## 0) + +#define APPDELTAITEM(str,a1) APP1(QString(str) + QString(", total %1
\n"), a1) + +#define APPITEMTEST(bold,str,a1,r,g,b) \ + if (a1 ## 1 - a1 ## 0 > 0.0000) \ + { \ + APPCOLTXT(r, g, b, bold ": "); \ + APPITEM (str, a1); \ + } + +#define APPDELTAITEMTEST(bold,str,a1,r,g,b) \ + if (a1 > 0.0000) \ + { \ + APPCOLTXT(r, g, b, bold ": "); \ + APP1(str ", total %1
\n", a1); \ + } + +#define APPITEMTESTBOLD(bold,str,a1) \ + if (a1 ## 1 - a1 ## 0 > 0.0000) \ + { \ + m_readoutHtml.append(QString("%1%2 ").arg(indent).arg(bold));\ + APPITEM (str, a1); \ + } + + +void +GLView::generateHtml() +{ + m_readoutHtml = ""; + QString indent; + QString spaces = QString("      "); + + if (m_readoutSample < 0 || m_readoutSample >= m_data.size()) return; + + const DataElement& e = m_data[m_readoutSample]; + const DataElement& elast = m_data[(m_readoutSample == 0) ? 0 : m_readoutSample - 1]; + + APP0 (""); + APP1 ("Start with frame %1, ", elast.frame); + APP1 ("swap to frame %1.", e.frame); + APPBR(); + APPBR(); + + APPCOLTXT ((e.repeatframe ? 0.6 : 1), .3, .3, "render start: "); + APPITEM ("from begin of GLView::paint, until we wait for swap", e.render); + + indent = spaces; + APPCOLTXT (.3, 1, .3, "evaluate (render) start: "); + APPITEM ("Evaluation during Session::render (Session::evaluateForDisplay)", e.eval); + + if (e.cacheTest0 < e.eval1) + { + QString saveIndent = indent; + indent = saveIndent+spaces; + APPITEMTEST ("cacheTest", "test to see if image is in cache, IPGraph::evaluateAtFrame()", e.cacheTest, 1, 1, 0); + + indent = saveIndent+spaces+spaces; + APPITEMTESTBOLD ("cacheTestLock", "acquire lock on frame cache", e.cacheTestLock); + APPITEMTESTBOLD ("setDisplayFrame", "set display frame on frame cache", e.setDisplayFrame); + APPITEMTESTBOLD ("frameCachedTest", "check that all FBs for this frame are in cache", e.frameCachedTest); + + indent = saveIndent+spaces; + APPITEMTESTBOLD ("evalGraph", "get the FBs for this frame from cache.", e.evalGraph); + } + + + indent = spaces; + APPCOLTXT (.3, 1, .3, "evaluate (render) end"); + APPBR(); + + APPCOLTXT (1, .6, .6, "internalRender: "); + APPITEM ("Internal render time, total time in CompositeRenderer::render", e.internalRender); + + indent = spaces+spaces; + APPITEMTEST ("userRender", "userRender code (handling \"render\" event). only time delta is valid.", e.userRender, .6, .6, 1); + APPITEMTEST ("frameChange", "internal frameChangedMessage processing plug \"frame-changed\" user event.", e.frameChange, 1, 1, .6); + + APPDELTAITEMTEST ("renderFenceWait", "total fence wait time. should only be non-zero in presentation mode.", e.renderFenceWait, .6, .2, .2); + APPDELTAITEMTEST ("renderUploadPlane", "total time in uploadPlane(). should only be non-zero if upload did not complete in prefetch.", e.renderUploadPlane, .6, .2, .6); + + indent = spaces; + APPCOLTXT (1, .6, .6, "internalRender end"); + APPBR(); + + indent = ""; + APPCOLTXT ((e.repeatframe ? 0.6 : 1), .3, .3, "render end"); + APPBR(); + + float swapDiff = e.swap1 - e.swap0; + float swapRate = (swapDiff == 0.0) ? 0.0 : 1.0/swapDiff; + + QString swapText; + if (swapRate != 0.0 && m_computedRefresh != 0.0 && swapRate < 1.0/m_computedRefresh) + { + swapText = QString("rendering complete, waiting for buffer swap, equiv rate = %1").arg(swapRate); + } + else + { + swapText = QString("rendering complete, waiting for buffer swap, equiv rate = %1").arg(swapRate); + } + APPITEMTEST ("buffer swap / vsync", swapText, e.swap, .3, .3, .75); + + APPITEMTEST ("awaken", "awaken caching threads post-render", e.awaken, .2, .6, .6); + + APPCOLTXT (.7, 1, 1, "prefetch: "); + APPITEM ("time spent in Session::postRender, including eval time if prefetching.", e.prefetch); + + if (e.cacheTest1 > e.prefetch0) + { + QString saveIndent = indent; + indent = saveIndent+spaces; + APPITEMTEST ("cacheTest", "test to see if image is in cache, IPGraph::evaluateAtFrame()", e.cacheTest, 1, 1, 0); + + indent = saveIndent+spaces+spaces; + + APPITEMTESTBOLD ("cacheTestLock", "acquire lock on frame cache", e.cacheTestLock); + APPITEMTESTBOLD ("setDisplayFrame", "set display frame on frame cache", e.setDisplayFrame); + APPITEMTESTBOLD ("frameCachedTest", "check that all FBs for this frame are in cache", e.frameCachedTest); + + indent = saveIndent+spaces; + APPITEMTESTBOLD ("evalGraph", "get the FBs for this frame from cache.", e.evalGraph); + } + + indent = spaces; + APPCOLTXT (.6, .2, .2, "prefetchRender: "); + APPITEM ("total prefetch rendertime, CompositeRenderer::prefetch.", e.prefetchRender); + + indent = spaces+spaces; + APPDELTAITEMTEST ("prefetchUploadPlanes", "time spent in uploadPlanes, may be zero of upload is async", e.prefetchUploadPlane, .6, .2, .6); + + indent = spaces; + APPCOLTXT (.6, .2, .2, "prefetchRender end "); + APPBR(); + + indent = ""; + APPCOLTXT (.7, 1, 1, "prefetch end"); + APPBR(); + + APPBR(); + APP0 ("Cache Node Evaluation Items (CacheIPNode::evaluate) These may be garbled by timing issues if caching is happening during playback.
"); + + indent = spaces; + APPITEMTEST ("evalID", "evaluate identifiers preparatory to looking them up in the cache", e.evalID, .75, 1, .75); + APPITEMTEST ("cacheQuery", "evaluate identifiers preparatory to looking them up in the cache.", e.cacheQuery, 0, .75, 1); + APPITEMTEST ("cacheEval", "actually evaluate the FBs for this frame.", e.cacheEval, 1, .6, 1); + APPITEMTESTBOLD ("IO", "FileSource calling imagesAtFrame on it's movies.", e.io); + + indent = ""; + + APPBR(); + APP1 ("%1 Garbage collections so far.", e.gccount); + APPBR(); + APP3 ("Expected time of next vsync: %1, which is in the %2 (%3 sec from end of render).", + e.expectedSyncTime, ((e.expectedSyncTime < e.render1) ? "past" : "future"), (e.expectedSyncTime-e.render1)); + if (e.deviceClockOffset != 0.0) + { + APPBR(); + APP1 ("Elapsed play time - Device clock = %1 seconds.", e.deviceClockOffset); + } + + APP0 (""); + // cerr << m_readoutHtml.toStdString() << endl; +} + +int +GLView::sampleFromMousePosition(float x, float y) +{ + float tx = m_startTime + (x / width()) * (m_endTime - m_startTime); + float ty = m_startTime + (-y / height()) * (m_endTime - m_startTime) * (height() / width()); + + Matrix m = m_matrix; + m.invert(); + Vec t = m * Vec(tx, ty, 0); + + return timeToSample (t.x); +} + +void +GLView::mouseMoveEvent(QMouseEvent* event) +{ + QPoint dp = event->pos() - m_mouseDown; + float w = width(); + float h = height(); + + if (event->buttons() == Qt::NoButton) + { + int sample = sampleFromMousePosition (event->pos().x(), event->pos().y()); + + if (sample != m_readoutSample) + { + m_readoutSample = sample; + generateHtml(); + m_readout->setHtml(m_readoutHtml); + } + return; + } + + if (event->buttons() & Qt::RightButton) + { + Vec t(.5 * (m_endTime - m_startTime), + .5 * (m_endTime - m_startTime) * (h / w), + 0); + + float a = dp.x() * .8; + a *= 0.001; + a += 1.0; + m_matrix = + translationMatrix(t) + * scaleMatrix(Vec(a, a, 1)) + * translationMatrix(-t) + * m_matrix; + } + else + if (event->buttons() & Qt::LeftButton) + { + Vec t = Vec(dp.x() / w * (m_endTime - m_startTime), + -dp.y() / h * (m_endTime - m_startTime) * (h / w), + 0); + m_matrix = translationMatrix(t) * m_matrix; + } + m_mouseDown = event->pos(); + updateGL(); +} + +void +GLView::autoRangeProcessing() +{ + if (m_rangeEnd > m_rangeStart) + { + m_visWin->updateTimeRangeFromGLView(); + m_visWin->rangeChanged(); + m_actualFPS = float(int(m_computedFPS + 0.5)); + m_visWin->updateActualFPSFromGLView(); + m_showIdealFrames = true; + m_visWin->setShowIdealFrames(); + } +} + +void +GLView::mousePressEvent(QMouseEvent* event) +{ + m_mouseDown = event->pos(); + + if (event->buttons() & Qt::LeftButton) + { + if (event->modifiers() & Qt::ShiftModifier) + { + int sample = sampleFromMousePosition (event->pos().x(), event->pos().y()); + + m_rangeStartIndex = sample; + while (m_rangeStartIndex > 1) + { + const DataElement& one = m_data[m_rangeStartIndex-1]; + const DataElement& two = m_data[m_rangeStartIndex]; + if (one.expectedSyncTime == 0.0 || two.expectedSyncTime == 0.0) break; + if (one.frame > two.frame) break; + --m_rangeStartIndex; + } + ++m_rangeStartIndex; + int frame = m_data[m_rangeStartIndex].frame; + while (frame == m_data[m_rangeStartIndex].frame) ++m_rangeStartIndex; + + m_rangeStart = m_data[m_rangeStartIndex].render0; + + m_rangeEndIndex = sample; + while (m_rangeEndIndex < m_data.size()-1) + { + const DataElement& one = m_data[m_rangeEndIndex-1]; + const DataElement& two = m_data[m_rangeEndIndex]; + if (one.expectedSyncTime == 0.0 || two.expectedSyncTime == 0.0) break; + if (one.frame > two.frame) break; + ++m_rangeEndIndex; + } + --m_rangeEndIndex; + frame = m_data[m_rangeEndIndex].frame; + while (frame == m_data[m_rangeEndIndex].frame) --m_rangeEndIndex; + + m_rangeEnd = m_data[m_rangeEndIndex].swap1; + + autoRangeProcessing(); + } + else + if (event->modifiers() & Qt::ControlModifier) + { + int sample = sampleFromMousePosition (event->pos().x(), event->pos().y()); + m_rangeStart = m_data[sample].render0; + m_rangeStartIndex = sample; + autoRangeProcessing(); + } + else + if (event->modifiers() & Qt::AltModifier) + { + int sample = sampleFromMousePosition (event->pos().x(), event->pos().y()); + m_rangeEnd = m_data[sample].swap1; + m_rangeEndIndex = sample; + autoRangeProcessing(); + } + } + updateGL(); +} + +void +GLView::mouseReleaseEvent(QMouseEvent* event) +{ +} + +void +GLView::wheelEvent(QWheelEvent* event) +{ + float w = width(); + float h = height(); + + Vec t(.5 * (m_endTime - m_startTime), + .5 * (m_endTime - m_startTime) * (h / w), + 0); + + float a = event->delta() * .8; + a *= 0.001; + a += 1.0; + m_matrix = + translationMatrix(t) + * scaleMatrix(Vec(a, a, 1)) + * translationMatrix(-t) + * m_matrix; + + updateGL(); +} + +void +GLView::initializeGL() +{ + Context c = GLtext::newContext(); + GLtext::setContext(c); + GLtext::init(); +} + +void +GLView::resizeGL(int, int) +{ + glViewport(0, 0, width(), height()); + updateGL(); +} + +static void drawBox( + float r, + float g, + float b, + float a, + float llx, + float lly, + float urx, + float ury) +{ + glColor4f(r, g, b, a); + glBegin(GL_POLYGON); + glVertex2f(llx, lly); + glVertex2f(llx, ury); + glVertex2f(urx, ury); + glVertex2f(urx, lly); + glEnd(); +} + +static void drawBox( + float r, + float g, + float b, + float a, + float x0, + float y0, + float x1, + float y1, + float x2, + float y2, + float x3, + float y3) +{ + glColor4f(r, g, b, a); + glBegin(GL_POLYGON); + glVertex2f(x0, y0); + glVertex2f(x1, y1); + glVertex2f(x2, y2); + glVertex2f(x3, y3); + glEnd(); +} + +void +GLView::paintGL() +{ + glViewport(0, 0, width(), height()); + glClearColor(0.5f, 0.5f, 0.5f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + if (m_data.empty()) { glFinish(); return; } + int idealStartIndex = -1; + int lastIdealFrame = -1; + +#ifndef PLATFORM_WINDOWS + glEnable(GL_MULTISAMPLE); +#endif + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + double l = (m_endTime - m_startTime) / double(width()); + gluOrtho2D(0, m_endTime - m_startTime, 0, l * double(height())); + glEnable(GL_POLYGON_SMOOTH); + + glMatrixMode(GL_MODELVIEW); + glLoadMatrix(m_matrix); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if (m_rangeStart != 0 && m_rangeEnd != 0 && m_rangeEnd != m_rangeStart) + { + drawBox(1, 1, .8, .1, m_rangeStart, -0.05, m_rangeEnd, 0.08); + } + + float h0 = 0; + float h = .05; + double x0, x1, y0, y1; + + for (size_t i = 0; i < m_data.size(); i++) + { + const DataElement& e = m_data[i]; + + // + // Red box: all GLview paint time; many other times are + // fractions of this time, so drawn over this box. brighter for + // the first time we've rendered this frame. This includs all of + // Sesssion::render(). + // + + y0 = h0; + y1 = h/3.0; + + drawBox(e.repeatframe ? 0.6 : 1, .3, .3, 1, e.render0, y0, e.render1, y1); + + // + // Green box, drawn above render box. corresponds to Session's evaluateForDisplay(). + // + + y0 = y1; + y1 = 2*h/3.0; + + drawBox (.3, 1, .3, 1, e.eval0, y0, e.eval1, y1); + + if (m_showEvalTiming) + { + // + // Eval time components may come from evalFrameForDisplay (part of + // render), or later (in same frame) as part of prefetch + // (postRender()). + // + // So these may be part of the R0-R1 (render) interval, or of the + // PR0-P01 (prefetch) interval; draw them so they can work either + // way. Horizontally in upper third. + // + + y0 = y1; + y1 = h; + + // + // Yellow: Testing to see if image is already in cache, first part + // of IPGraph::evaluateAtFrame() + // + + drawBox (1, 1, 0, 1, e.cacheTest0, y0, e.cacheTest1, y1); + + // + // Below three happen in CacheNode::evaluate, so if caching is + // happening during playback, timing here is likely to be messed + // up (IE numbers here may have nothing to do with this frame). + // + + // + // Light green: In CacheNode::evaluate, evaluate the IDs + // prepratory to testing for presence in cache. + // + + drawBox (.75, 1, .75, 1, e.evalID0, y0, e.evalID1, y1); + + // + // Sky blue: Now we have the IDs, query the cache to see if they + // are there. + // + + drawBox (0, .75, 1, 1, e.cacheQuery0, y0, e.cacheQuery1, y1); + + // + // Light magenta: If we didn't find our frame buffers in the + // cache, evaluate cache node input to get them. This color only + // appears if the display thread does the eval (caching is off). + // + + drawBox (1, .6, 1, 1, e.cacheEval0, y0, e.cacheEval1, y1); + } + + // + // Light red. Internal render time (total time in CompositeRenderer::render() + // Disjoint from eval time in total paint time, so draw in 2nd vertical third. + // + + y0 = h/3.0; + y1 = 2.0*h/3.0; + + drawBox (1, .6, .6, 1, e.internalRender0, y0, e.internalRender1, y1); + + // + // Next four are subsets of Internal Render, so draw above in top vertical third. + // + + y0 = y1; + y1 = h; + + // + // Light blue. Time spent in user render (this is now subset of + // internal rendertime), but exact timing can be mixed with other + // stuff, so only use time delta. + // + + x0 = e.internalRender0; + x1 = x0 + e.userRender1 - e.userRender0; + + drawBox (.6, .6, 1, 1, x0, y0, x1, y1); + + // + // Light yellow. Time spent in "frame change", both internal + // frameChangedMessage() processing, and user event "frame-changed". + // + + x0 = x1; + x1 = x0 + e.frameChange1 - e.frameChange0; + + drawBox (1, 1, .6, 1, x0, y0, x1, y1); + + // + // dark green. Total time spent waiting on glfences in render, should + // only be non-zero in presentation mode. + // + + x0 = x1; + x1 = x0 + e.renderFenceWait; + + drawBox (.6, .2, .2, 1, x0, y0, x1, y1); + + // + // dark magenta. Total time spent in uploadPlane() in render, should + // only be non-zero if texture uploads did not complete during prefetch. + // + + x0 = x1; + x1 = x0 + e.renderUploadPlane; + + drawBox (.6, .2, .6, 1, x0, y0, x1, y1); + + // + // Dark blue. Swap wait. Full height + // + + y0 = 0; + y1 = h; + drawBox (.3, .3, .75, 1, e.swap0, y0, e.swap1, y1); + + // + // Dark Cyan. Awakening caching threads (part of + // Session::postRender). Full height + // + + y0 = 0; + y1 = h; + drawBox (.2, .6, .6, 1, e.awaken0, y0, e.awaken1, y1); + + // + // Light cyan. Prefetch (time spend in Session::postRender(). If + // prefetch is on, this includes eval time (for next frame). + // + + y0 = h/3.0; + y1 = 2.0*h/3.0; + + if (e.prefetch0 != 0.0 && e.prefetch1 != 0.0) + { + drawBox (.7, 1, 1, 1, e.prefetch0, y0, e.prefetch1, y1); + } + + // + // Eval timing drawn above may appear in upper third above general + // prefetch time. After eval, we do some parts of the next "render", + // basically starting texture uploads in prep for shader execution. + // This is asynchronous, so should not take much time. Draw details + // in upper third. + // + + y0 = 2.0*h/3.0; + y1 = h; + + // + // dark magenta. Total time spent in uploadPlane() in prefetch, should + // really never be non-zero (because it should be asynchronous). + // + + if (e.prefetchRender0 != 0.0 && e.prefetchRender1 != 0.0) + { + x0 = e.prefetchRender0; + x1 = x0 + e.prefetchUploadPlane; + + drawBox (.6, .2, .6, 1, x0, y0, x1, y1); + } + + // + // Dark red. remainder of prefetch render time (not spent in + // uploadPlane). + // + + if (e.prefetchRender0 != 0.0 && e.prefetchRender1 != 0.0) + { + x0 = x1; + x1 = e.prefetchRender1; + + drawBox(0.6, .2, .2, 1, x0, y0, x1, y1); + } + } + + // + // Draw text last + // + + for (size_t i = 0; i < m_data.size(); i++) + { + const DataElement& e = m_data[i]; + + // + // Swap tick + // + glLineWidth(2.0); + glColor4f(0,0,0,.5); + glBegin(GL_LINES); + glVertex2f(e.swap1, h); + glVertex2f(e.swap1, h + h * .1); + glEnd(); + + // + // Target swap time for this frame. + // + if (e.expectedSyncTime != 0.0) + { + glColor4f(.7,.2,.2,1); + glBegin(GL_LINES); + glVertex2f(e.render0, 0); + glVertex2f(e.expectedSyncTime, h); + glVertex2f(e.expectedSyncTime, h + h * .05); + glEnd(); + + GLtext::size(20.0); + GLtext::color(.7, .2, .2, 1); + float s = 1.0 / (GLtext::globalAscenderHeight() - GLtext::globalDescenderHeight()); + ostringstream str; + str << e.frame; + glPushMatrix(); + glTranslatef(e.expectedSyncTime, h + h * .05, 0); + glScalef(s * .0035, s * .0035, 1.0); + GLtext::writeAt(0, 0, str.str()); + glPopMatrix(); + } + + // + // Frame flags + // + if (!e.repeatframe) + { + GLtext::size(20.0); + GLtext::color(0, 0, 0, .7); + float s = 1.0 / (GLtext::globalAscenderHeight() - GLtext::globalDescenderHeight()); + ostringstream str; + str << e.frame; + + Box2f b = GLtext::bounds(str.str()); + float tw = (b.max.x - b.min.x) * s * 0.0065; + float th = (b.max.y - b.min.y) * s * 0.0065; + float slop = .0008; + + glColor4f(1,1,1,.5); + glBegin(GL_POLYGON); + glVertex2f(e.swap1 - slop, h + h * .11 - slop); + glVertex2f(e.swap1 + tw + slop, h + h * .11 - slop); + glVertex2f(e.swap1 + tw + slop * 6, h + h * .11 + th / 2); + glVertex2f(e.swap1 + tw + slop, h + h * .11 + th + slop); + glVertex2f(e.swap1 - slop, h + h * .11 + th + slop); + glEnd(); + + glPushMatrix(); + glTranslatef(e.swap1, h + h * .11, 0); + glScalef(s * .0065, s * .0065, 1.0); + GLtext::writeAt(0, 0, str.str()); + glPopMatrix(); + } + + // + // Mark frames only drawn once. + // + if (e.singleFrame) + { + glColor4f(0, 0, 0,.7); + glLineWidth(2.0); + glBegin(GL_LINES); + glVertex2f(e.swap1, h + h * .25); + glVertex2f(e.swap1, h + h * .4); + glEnd(); + + GLtext::size(50.0); + GLtext::color(1, 1, 1, 1); + float s = 1.0 / (GLtext::globalAscenderHeight() - GLtext::globalDescenderHeight()); + ostringstream str; + str << "*1 "; + + Box2f b = GLtext::bounds(str.str()); + float tw = (b.max.x - b.min.x) * s * 0.0065; + float th = (b.max.y - b.min.y) * s * 0.0065; + float slop = .0008; + + glColor4f(1,.5,0,.8); + glBegin(GL_POLYGON); + glVertex2f(e.swap1 - slop, h + h * .4 - slop); + glVertex2f(e.swap1 + tw + slop, h + h * .4 - slop); + glVertex2f(e.swap1 + tw + slop, h + h * .4 + th + slop); + glVertex2f(e.swap1 - slop, h + h * .4 + th + slop); + glEnd(); + + glPushMatrix(); + glTranslatef(e.swap1, h + h * .4, 0); + glScalef(s * .0065, s * .0065, 1.0); + GLtext::writeAt(0, 0, str.str()); + glPopMatrix(); + } + + if (e.collectionOccured) + { + glColor4f(0, 0, 0,.7); + glLineWidth(2.0); + glBegin(GL_LINES); + glVertex2f(e.swap1, h + h * .25); + glVertex2f(e.swap1, h + h * .7); + glEnd(); + + GLtext::size(20.0); + GLtext::color(0, 0, 0, .85); + float s = 1.0 / (GLtext::globalAscenderHeight() - GLtext::globalDescenderHeight()); + ostringstream str; + str << "GC# " << e.gccount << " "; + + Box2f b = GLtext::bounds(str.str()); + float tw = (b.max.x - b.min.x) * s * 0.0100; + float th = (b.max.y - b.min.y) * s * 0.0100; + float slop = .0008; + + glColor4f(1,0,1,.9); + glBegin(GL_POLYGON); + glVertex2f(e.swap1 - slop, h + h * .7 - slop); + glVertex2f(e.swap1 + tw + slop, h + h * .7 - slop); + glVertex2f(e.swap1 + tw + slop, h + h * .7 + th + slop); + glVertex2f(e.swap1 - slop, h + h * .7 + th + slop); + glEnd(); + + glPushMatrix(); + glTranslatef(e.swap1, h + h * .7, 0); + glScalef(s * .0100, s * .0100, 1.0); + GLtext::writeAt(0, 0, str.str()); + glPopMatrix(); + } + + if (false && m_showIdealFrames && i >= m_rangeStartIndex && i <= m_rangeEndIndex) + { + if (idealStartIndex == -1) + { + if (!e.repeatframe) + { + idealStartIndex = i; + lastIdealFrame = m_data[idealStartIndex].frame; + } + } + else + { + int startFrame = m_data[idealStartIndex].frame; + int frame = startFrame + int(0.5 + ((e.swap1 - m_data[idealStartIndex].swap1)*m_actualFPS)); + + /* + int nsyncs = i - m_rangeStartIndex; + double t = nsyncs * m_computedRefresh; + double t0 = (nsyncs - 1) * m_computedRefresh; + int frame = m_actualFPS * t + m_data[m_rangeStartIndex].frame; + int previous = m_actualFPS * t0 + m_data[m_rangeStartIndex].frame; + + if (frame != previous) + { + */ + if (frame != lastIdealFrame) + { + lastIdealFrame = frame; + GLtext::size(20.0); + GLtext::color(0, 0, 0, .7); + float s = 1.0 / (GLtext::globalAscenderHeight() - GLtext::globalDescenderHeight()); + ostringstream str; + str << frame; + + Box2f b = GLtext::bounds(str.str()); + float tw = (b.max.x - b.min.x) * s * 0.0065; + float th = (b.max.y - b.min.y) * s * 0.0065; + float slop = .0008; + + glColor4f(.5,1,1,.5); + glBegin(GL_POLYGON); + glVertex2f(e.swap1 - slop, h + h * .21 - slop); + glVertex2f(e.swap1 + tw + slop, h + h * .21 - slop); + glVertex2f(e.swap1 + tw + slop * 6, h + h * .21 + th / 2); + glVertex2f(e.swap1 + tw + slop, h + h * .21 + th + slop); + glVertex2f(e.swap1 - slop, h + h * .21 + th + slop); + glEnd(); + + glPushMatrix(); + glTranslatef(e.swap1, h + h * .21, 0); + glScalef(s * .0065, s * .0065, 1.0); + GLtext::writeAt(0, 0, str.str()); + glPopMatrix(); + } + } + } + } + + if (m_showIdealFrames) + { + idealStartIndex = m_rangeStartIndex; + + const DataElement& e = m_data[0]; + int i; + for (i = m_rangeStartIndex; m_data[i].repeatframe; ++i) ; + + int startFrame = m_data[i].frame; + double time = m_data[i].swap1; + double endTime = m_data[m_rangeEndIndex].swap1; + + for (int frame = startFrame+1; time < endTime; ++frame) + { + time = time + 1.0/m_actualFPS; + + GLtext::size(20.0); + GLtext::color(0, 0, 0, .7); + float s = 1.0 / (GLtext::globalAscenderHeight() - GLtext::globalDescenderHeight()); + ostringstream str; + str << frame; + + Box2f b = GLtext::bounds(str.str()); + float tw = (b.max.x - b.min.x) * s * 0.0065; + float th = (b.max.y - b.min.y) * s * 0.0065; + float slop = .0008; + + glColor4f(.5,1,1,.5); + glBegin(GL_POLYGON); + glVertex2f(time - slop, h + h * .21 - slop); + glVertex2f(time + tw + slop, h + h * .21 - slop); + glVertex2f(time + tw + slop * 6, h + h * .21 + th / 2); + glVertex2f(time + tw + slop, h + h * .21 + th + slop); + glVertex2f(time - slop, h + h * .21 + th + slop); + glEnd(); + + glPushMatrix(); + glTranslatef(time, h + h * .21, 0); + glScalef(s * .0065, s * .0065, 1.0); + GLtext::writeAt(0, 0, str.str()); + glPopMatrix(); + } + } + + glColor4f(0,0,0,.5); + + glLineWidth(2.0); + glBegin(GL_LINES); + glVertex2f(m_startTime, 0); + glVertex2f(m_endTime, 0); + glVertex2f(m_startTime, 0); + glVertex2f(m_startTime, h * 2.0); + glVertex2f(m_endTime, 0); + glVertex2f(m_endTime, h * 2.0); + glEnd(); + + GLtext::size(100.0); + GLtext::color(0, 0, 0, .5); + float s = 1.0 / (GLtext::globalAscenderHeight() - GLtext::globalDescenderHeight()); + + for (int i = int(m_startTime); i < int(m_endTime - m_startTime + 1.0); i++) + { + glBegin(GL_LINES); + glVertex2f(float(i), 0); + glVertex2f(float(i), -0.01); + glVertex2f(float(i) + 0.5, 0); + glVertex2f(float(i) + 0.5, -0.008); + glEnd(); + + { + ostringstream str; + str << i << " sec"; + glPushMatrix(); + glTranslatef(float(i), float(-0.03), 0); + glScalef(s * .02, s * .02, 1.0); + GLtext::writeAt(0, 0, str.str()); + glPopMatrix(); + } + + { + ostringstream str; + str << i << ".5 sec"; + glPushMatrix(); + glTranslatef(float(i) + 0.5, float(-0.03), 0); + glScalef(s * .02, s * .02, 1.0); + GLtext::writeAt(0, 0, str.str()); + glPopMatrix(); + } + + //renderText(float(i), -0.01, 0, QString("%1").arg(i)); + } + + glFinish(); +} + diff --git a/src/bin/apps/rvprof/VisMainWindow.h b/src/bin/apps/rvprof/VisMainWindow.h new file mode 100644 index 000000000..13d478e82 --- /dev/null +++ b/src/bin/apps/rvprof/VisMainWindow.h @@ -0,0 +1,200 @@ +// +// Copyright (c) 2008 Tweak Software. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +#ifndef __playbackVis__VisMainWindow__h__ +#define __playbackVis__VisMainWindow__h__ +#include +#include +#include +#include + +#include +#include +#include +#include "ui_VisMainWindow.h" +#include "ui_DockWidget.h" +#include "ui_FileViewDialog.h" + +struct DataElement +{ + double render0; + double render1; + double swap0; + double swap1; + double eval0; + double eval1; + double userRender0; + double userRender1; + double frameChange0; + double frameChange1; + double internalRender0; + double internalRender1; + double prefetch0; + double prefetch1; + + double expectedSyncTime; + double deviceClockOffset; + int gccount; + int frame; + + double cacheTest0; + double cacheTest1; + double evalGraph0; + double evalGraph1; + double evalID0; + double evalID1; + double cacheQuery0; + double cacheQuery1; + double cacheEval0; + double cacheEval1; + double io0; + double io1; + + double restartA0; + double restartA1; + double restartB0; + double restartB1; + double cacheTestLock0; + double cacheTestLock1; + double setDisplayFrame0; + double setDisplayFrame1; + double frameCachedTest0; + double frameCachedTest1; + double awaken0; + double awaken1; + + double prefetchRender0; + double prefetchRender1; + double prefetchUploadPlane; + double renderUploadPlane; + double renderFenceWait; + + bool repeatframe; + bool collectionOccured; + bool singleFrame; + + double start; + double end; +}; + +struct NameOffsetPair +{ + const char* name; + long offset; +}; + +typedef std::vector DataVector; + +class VisMainWindow; + +class GLView : public QGLWidget +{ + Q_OBJECT + + public: + + typedef TwkMath::Mat44f Matrix; + typedef TwkMath::Vec3f Vec; + + GLView(QWidget* parent, QTextEdit* readout, VisMainWindow* visWin); + + void setData(const DataVector&); + DataVector& data() { return m_data; } + + void setRange(int start, int end); + void setTimeRange(float start, float end); + size_t startIndex() const { return m_rangeStartIndex; } + size_t endIndex() const { return m_rangeEndIndex; } + double rangeStart() const { return m_rangeStart; } + double rangeEnd() const { return m_rangeEnd; } + float actualFPS() const { return m_actualFPS; } + + void setActualFPS(float fps); + void setShowIdealFrames(bool b); + void setShowEvalTiming(bool b); + void setComputedRefresh(float); + void setComputedFPS(float); + + protected: + void initializeGL(); + void paintGL(); + void resizeGL(int, int); + void rebuildIdealFrames(); + int timeToSample(float t); + int sampleFromMousePosition(float x, float y); + void generateHtml(); + void autoRangeProcessing(); + + virtual void mouseMoveEvent(QMouseEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + virtual void mouseReleaseEvent(QMouseEvent* event); + virtual void wheelEvent(QWheelEvent* event); + + private: + DataVector m_data; + double m_startTime; + double m_endTime; + float m_scale; + float m_xtran; + float m_ytran; + + float m_actualFPS; + bool m_showIdealFrames; + bool m_showEvalTiming; + float m_computedRefresh; + float m_computedFPS; + + float m_rangeStart; + float m_rangeEnd; + size_t m_rangeStartIndex; + size_t m_rangeEndIndex; + + Matrix m_matrix; + + QPoint m_mouseDown; + + QString m_readoutHtml; + QTextEdit* m_readout; + int m_readoutSample; + VisMainWindow* m_visWin; +}; + +class VisMainWindow : public QMainWindow +{ + Q_OBJECT + + public: + VisMainWindow(QWidget *parent = 0); + + void readFile(const QString&); + bool eventFilter(QObject *obj, QEvent *event); + void updateTimeRangeFromGLView(); + void updateActualFPSFromGLView(); + void setShowIdealFrames(); + + public slots: + void rangeChanged(); + void showIdealFrames(int); + + private slots: + void openFile(); + void showFile(); + void quit(); + void fpsChanged(); + void showEvalTiming(int); + + private: + GLView* m_glWidget; + Ui::VisMainWindow m_ui; + Ui::DockWidget m_dockUI; + Ui::FileViewDialog m_fileViewUI; + QDockWidget* m_dockWidget; + QDialog* m_fileViewDialog; + double m_mouseTime; +}; + +#endif // __playbackVis__VisMainWindow__h__ diff --git a/src/bin/apps/rvprof/VisMainWindow.ui b/src/bin/apps/rvprof/VisMainWindow.ui new file mode 100644 index 000000000..c205f12ab --- /dev/null +++ b/src/bin/apps/rvprof/VisMainWindow.ui @@ -0,0 +1,70 @@ + + + VisMainWindow + + + + 0 + 0 + 1522 + 736 + + + + RV Profile Viewer + + + + + + 0 + 0 + 1522 + 22 + + + + + File + + + + + + + Tools + + + + + + + + + + Open... + + + Ctrl+O + + + + + Quit + + + Ctrl+Q + + + + + Show Raw Profile Data... + + + Ctrl+F + + + + + + diff --git a/src/bin/apps/rvprof/main.cpp b/src/bin/apps/rvprof/main.cpp new file mode 100644 index 000000000..2d649f15c --- /dev/null +++ b/src/bin/apps/rvprof/main.cpp @@ -0,0 +1,49 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +#include "../../utf8Main.h" + +#include "VisMainWindow.h" +#include + +using namespace std; + +const string usage = "\ +\n\ +usage: rvprof \n\ +\n\ +mouse:\n\ + left-click drag to pan camera\n\ + right-click drag or scroll-wheel to zoom in/out\n\ + control-left-click to set start point of refresh rate computation\n\ + alt-left-click to set end point of refresh rate computation\n\ + shift-left-click to bracket start/end point of refresh rate computation on play range\n\ +\n\ +fields:\n\ + input frame or time range to comput 'real' refresh rate\n\ + input real target fps to generate 'ideal' frame transitions\n\ +\n\ +"; + +int utf8Main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + VisMainWindow mainWindow; + + if (argc == 2) + { + if (0 == strcmp(argv[1], "-help")) + { + cerr << usage; + exit (-1); + } + else mainWindow.readFile(argv[1]); + } + + mainWindow.show(); + mainWindow.raise(); + return app.exec(); +} diff --git a/src/bin/apps/rvprof/rvprof.wrapper b/src/bin/apps/rvprof/rvprof.wrapper new file mode 100644 index 000000000..667238e36 --- /dev/null +++ b/src/bin/apps/rvprof/rvprof.wrapper @@ -0,0 +1,55 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the RV_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set RV_HOME if invoked from a link to the script +# and RV_HOME is not already set +# + +set noglob + +# +# Uncomment this if you're on an older linux distro and RV is hanging in +# the Audio preferences or on startup with audio sources +# +#setenv PA_ALSA_EXCLUDE_DMIX_DEFAULT 1 + +# +# For unknown reasons, LANG causes problems when set to +# interesting values (like fr_FR.UTF-8). +# +unsetenv LANG + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +set platform = i386 +set pvbin = "$RV_HOME/bin/rvprof.bin.$platform" + +if (! (-e $pvbin) ) then + set pvbin = "$RV_HOME/bin/rvprof.bin" +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec bin + +exec $pvbin $*:q diff --git a/src/bin/apps/rvprof/utf8Main.cpp b/src/bin/apps/rvprof/utf8Main.cpp new file mode 100644 index 000000000..69e097cc9 --- /dev/null +++ b/src/bin/apps/rvprof/utf8Main.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.cpp" \ No newline at end of file diff --git a/src/bin/apps/rvpush/CMakeLists.txt b/src/bin/apps/rvpush/CMakeLists.txt new file mode 100644 index 000000000..a81251a5c --- /dev/null +++ b/src/bin/apps/rvpush/CMakeLists.txt @@ -0,0 +1,46 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "rvpush" +) + +SET(CMAKE_AUTOUIC + ON +) +SET(CMAKE_AUTOMOC + ON +) +SET(CMAKE_AUTORCC + ON +) + +SET(_sources + main.cpp utf8Main.cpp + # QT-specific source files + RvPusher.cpp +) +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +FIND_PACKAGE( + Qt5 + COMPONENTS Core Network Core + REQUIRED +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_LINK_LIBRARIES(${_target} TwkQtChat Qt5::Core Qt5::Network Qt5::Core) + +RV_STAGE(TYPE "EXECUTABLE_WITH_PLUGINS" TARGET ${_target}) diff --git a/src/bin/apps/rvpush/RvPusher.cpp b/src/bin/apps/rvpush/RvPusher.cpp new file mode 100644 index 000000000..0324c92ec --- /dev/null +++ b/src/bin/apps/rvpush/RvPusher.cpp @@ -0,0 +1,431 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +#include +#include +#include +#include +#include +#include +#include + +#ifdef PLATFORM_WINDOWS + #include // For Sleep +#else + #include // For usleep +#endif + +#if 0 +#define DB(x) cerr << dec << x << endl +#define DBL(level, x) cerr << dec << x << endl +#else +#define DB(x) +#define DBL(level, x) +#endif + +using namespace std; + +RvPusher::RvPusher(QCoreApplication &app, string tag, string cmd, vector argv, QObject *parent) : + QObject(parent), + m_app(app), + m_tag(tag), + m_command(cmd), + m_argv(argv), + m_error(0), + m_client(0), + m_rvStarted(false) +{ + // + // Make network client. If we can find a port number for a running rv, + // we'll connect to it later. + // + m_client = new TwkQtChat::Client("rvpush-1", "rvpush", 0, false); + + connect(m_client, SIGNAL(newContact(const QString &)), + this, SLOT(newContact(const QString &))); + + connect(m_client, SIGNAL(connectionFailed(QAbstractSocket::SocketError)), + this, SLOT(connectionFailed(QAbstractSocket::SocketError))); + + connect(m_client, SIGNAL(contactLeft(const QString &)), + this, SLOT(contactLeft(const QString &))); + + connect(m_client, SIGNAL(newMessage(const QString &, const QString &)), + this, SLOT(newMessage(const QString &, const QString &))); + + // + // Build list of possible tmp files holding rv port numbers. + // + buildFileList(); + + // + // Try the first possible port file. + // + m_currentFile = 0; + attemptConnection(); +} + +void +RvPusher::buildFileList() +{ + DB ("buildFileList"); + + QDir tmp = QDir::temp(); + + if (! tmp.exists ("tweak_rv_proc")) return; + + tmp.cd("tweak_rv_proc"); + + // + // Get files in this dir (not subdirs), sorted by modification time. + // + QString tagString(m_tag.c_str()); + + QFileInfoList rawList = tmp.entryInfoList (QDir::Files, QDir::Time); + + for (int i = 0; i < rawList.size(); ++i) + { + QStringList parts = rawList[i].fileName().split("_"); + if ((parts.size() == 1 && tagString == "") || (parts.size() == 2 && parts[1] == tagString)) + { + m_fileList.push_back(rawList[i]); + } + } + + for (int i = 0; i < m_fileList.size(); ++i) + { + DB (" file " << i << " '" << m_fileList[i].absoluteFilePath().toStdString() << "'"); + } +} + +void +RvPusher::attemptConnection() +{ + DB ("attemptConnection currentFile " << m_currentFile << " fileList:"); + for (int i = 0; i < m_fileList.size(); ++i) + { + DB (" file " << i << " '" << m_fileList[i].absoluteFilePath().toStdString() << "'"); + } + + // + // Attempt to read port number from currentFile, try other files if this + // fails. When we have a candidate port, attempt a connection. This is + // an async operation, so if it fails, contactLeft() will send us back here + // (with a new currentFile). + // + + int port = 0; + + while (!port && m_currentFile < m_fileList.size()) + { + port = 0; + QFile portFile (m_fileList[m_currentFile].absoluteFilePath()); + + if (portFile.open (QIODevice::ReadOnly)) + { + // + // Read port number + // + char buf[256]; + + int status = portFile.readLine (buf, 256); + portFile.close(); + + if (-1 != status) + { + QString str(buf); + bool ok; + port = str.toInt(&ok); + if (!ok) port = 0; + + DB (" '" << portFile.fileName().toStdString() << "' gives port " << port); + } + } + + if (!port) + { + // + // This file gives no port (is unreadable or mal-formed), so + // remove it and try the next one. + // + DB (" removing " << portFile.fileName().toStdString()); + QFile::remove (portFile.fileName()); + ++m_currentFile; + } + } + + if (!port) + { + // + // We've run out of files with no successful connection, so start a new rv, or exit. + // + + QString rvExe; + + if (getenv("RVPUSH_RV_EXECUTABLE_PATH")) rvExe = getenv("RVPUSH_RV_EXECUTABLE_PATH"); + else + { + // + // Assume that rv we'll run is in same dir as rvpush. + // + rvExe = QCoreApplication::applicationDirPath(); + + #if defined(PLATFORM_WINDOWS) + rvExe += "/rv.exe"; + #elif defined(PLATFORM_DARWIN) + rvExe += "/RV"; + #else + rvExe += "/rv"; + #endif + } + + if (rvExe == "none" || !QFileInfo(rvExe).isExecutable()) + { + cerr << "ERROR: cannot connect to any running RV" << endl; + m_error = 11; + } + else + { + cerr << "INFO: cannot connect to any running RV, starting new one" << endl; + + QStringList qargs; + + // + // Start a new rv with the appropriate tag. + // + if (m_tag != "") + { + qargs.push_back ("-networkTag"); + qargs.push_back (m_tag.c_str()); + } + + // + // New RV must be listening for future rvpush connections. + // + qargs.append("-network"); + + if (m_command == "mu-eval") qargs.append ("-eval"); + else if (m_command == "mu-eval-return") qargs.append ("-eval"); + else if (m_command == "py-eval") qargs.append ("-pyeval"); + else if (m_command == "py-eval-return") qargs.append ("-pyeval"); + else if (m_command == "py-exec") qargs.append ("-pyeval"); + + if (m_command == "url") + { + qargs.append (m_argv[0].c_str()); + } + else + { + for (int i = 0; i < m_argv.size(); ++i) + { + #ifdef PLATFORM_WINDOWS + if (m_command == "set" || m_command == "merge") + std::replace (m_argv[i].begin(), m_argv[i].end(), '\\', '/'); + #endif + qargs.append (m_argv[i].c_str()); + } + } + + DB (" starting '" << rvExe.toStdString() << "'"); + for (int i = 0; i < qargs.size(); ++i) DB (" " << qargs[i].toStdString()); + + QProcess::startDetached (rvExe, qargs); + + DB (" done"); + + m_rvStarted = true; + m_error = 15; + } + + m_app.quit(); + } + else + { + // + // We have a candidate port number, so try it. Success will send us + // to newContact(), failure to contactLeft(). + // + QHostAddress addr ("127.0.0.1"); + m_client->connectTo("rv", addr, port); + } +} + +void +RvPusher::newContact(const QString &contact) +{ + // + // We've successfully connected, so send our one eval event, and exit unless we're + // waiting for a return value. + // + + m_contact = contact; + DB ("connected to " << contact.toStdString()); + + bool rsvp = (m_command == "mu-eval-return" || m_command == "py-eval-return"); + + if ( m_command == "mu-eval" || + m_command == "mu-eval-return" || + m_command == "py-eval" || + m_command == "py-eval-return" || + m_command == "py-exec") + { + QString src = ""; + for (int i = 0; i < m_argv.size(); ++i) + { + src += m_argv[i].c_str(); + src += " "; + } + DB (" " << m_command << " '" << src.toStdString() << "'"); + + QString com = "invalid"; + + if (m_command == "mu-eval" || m_command == "mu-eval-return") com = "remote-eval"; + else if (m_command == "py-eval" || m_command == "py-eval-return") com = "remote-pyeval"; + else com = "remote-pyexec"; + + + bool rsvp = (m_command == "mu-eval-return" || m_command == "py-eval-return"); + + m_client->sendEvent(m_contact, com, "*", src, rsvp); + } + else + if (m_command == "set" || m_command == "merge") + { + // + // If "set" clear the session before we add media, otherwise merge it in. + // + QString mu = (m_command == "set") ? "{ require rvui; rvui.clearEverything(); " : "{ "; + + // + // Run through args, use '+' instead of '-' for per-source args. + // + + mu += "addSources(string[] {"; + bool inSource = false; + + for (int i = 0; i < m_argv.size(); ++i) + { + if (m_argv[i] == "[") inSource = true; + else + if (m_argv[i] == "]") inSource = false; + else + if (inSource && m_argv[i][0] == '-' && isalpha(m_argv[i][1])) m_argv[i][0] = '+'; + + // convert relative to absolute file path + QFileInfo curFileInfo(QString::fromStdString(m_argv[i])); + // Not all arguements are file path. We change only if the arguement is an existing file. + if (curFileInfo.exists() && curFileInfo.isRelative()) + { + m_argv[i] = curFileInfo.absoluteFilePath().toStdString(); + } + + #ifdef PLATFORM_WINDOWS + std::replace(m_argv[i].begin(), m_argv[i].end(), '\\', '/'); + #endif + + if (i) mu += ", \""; + else mu += "\""; + + mu += m_argv[i].c_str(); + + mu += "\""; + } + mu += "}, \"rvpush\", false, "; + mu += ((m_command == "merge") ? "true);" : "false);"); + mu += " mainWindowWidget().raise(); }"; + + DB (" " << m_command << " '" << mu.toStdString() << "'"); + + m_client->sendEvent( + m_contact, + "remote-eval", + "*", + mu, + false); + } + else + if (m_command == "url") + { + QString mu = "{ mainWindowWidget().raise(); "; + + mu += "sessionFromUrl(\""; + mu += m_argv[0].c_str(); + mu += "\"); }"; + + DB (" " << m_command << " '" << mu.toStdString() << "'"); + + m_client->sendEvent( + m_contact, + "remote-eval", + "*", + mu, + false); + } + + if (!rsvp) disconnect(); +} + +void +RvPusher::disconnect() +{ + m_client->signOff (m_contact); + // + // Allow time for the communication to finish before we bail. + // + #ifdef PLATFORM_WINDOWS + Sleep(250); + #else + usleep(250000); + #endif + m_app.quit(); +} + +void +RvPusher::connectionFailed(QAbstractSocket::SocketError error) +{ + // + // Not sure under what circumstances we'd end up here, but if we do we + // should quit + // + DB ("error"); + m_error = 4; + m_app.quit(); +} + +void RvPusher::contactLeft(const QString& contact) +{ + DB ("contactLeft " << contact.toStdString()); + + // + // Contact failed so remove this port file + // + DB (" removing " << m_fileList[m_currentFile].absoluteFilePath().toStdString()); + QFile::remove (m_fileList[m_currentFile].absoluteFilePath()); + + // + // Increment file counter and try again + // + ++m_currentFile; + attemptConnection(); +} + +void +RvPusher::newMessage (const QString& sender, const QString& inmessage) +{ + DB ("newMessage '" << inmessage.toStdString() << "'"); + + if (! inmessage.startsWith("RETURN ")) return; + + QString message = inmessage; + message.remove(0, 7); + + DB ("returned '" << message.toStdString() << "'"); + + cout << message.toStdString() << endl; + + disconnect(); +} + diff --git a/src/bin/apps/rvpush/RvPusher.h b/src/bin/apps/rvpush/RvPusher.h new file mode 100644 index 000000000..6f5554ad5 --- /dev/null +++ b/src/bin/apps/rvpush/RvPusher.h @@ -0,0 +1,69 @@ +// +// Copyright (c) 2008 Tweak Software. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +#ifndef __rvpush__RvPusher__h__ +#define __rvpush__RvPusher__h__ + +#if defined( _WIN32 ) +#include +#endif +#include +#include +#include + +using namespace std; + +class QCoreApplication; + +// +// RvPusher reads port number for the most recently started RV out of a tmp +// file and trys to communicate with it. Either setting or adding to the +// media, or running arbitrary mu code, according to the "command". If it +// can't find an RV it can talk to, it'll start a new one. + +class RvPusher : public QObject +{ + Q_OBJECT + +public: + RvPusher(QCoreApplication&, string, string, vector, QObject *parent = 0); + + // + // Errors can be encountered during construction, or later due to async + // network communication. + // + int error() { return m_error; }; + + // + // Did we start a new RV process ? + // + bool rvStarted() { return m_rvStarted; }; + +private slots: + void newContact(const QString&); + void connectionFailed(QAbstractSocket::SocketError); + void contactLeft(const QString&); + void newMessage (const QString&, const QString&); + +private: + void buildFileList(); + void attemptConnection(); + void disconnect(); + + QCoreApplication& m_app; + TwkQtChat::Client* m_client; + QString m_contact; + string m_command; + string m_tag; + vector m_argv; + int m_error; + int m_currentFile; + QFileInfoList m_fileList; + bool m_rvStarted; +}; + +#endif // __rvshell__ShellDialog__h__ diff --git a/src/bin/apps/rvpush/main.cpp b/src/bin/apps/rvpush/main.cpp new file mode 100644 index 000000000..801b35f02 --- /dev/null +++ b/src/bin/apps/rvpush/main.cpp @@ -0,0 +1,141 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +#include "../../utf8Main.h" + +#include +#include +#include + +using namespace std; + +const char* usage = "\n\ +usage: rvpush [-tag ] \n\ + rvpush set \n\ + rvpush merge \n\ + rvpush mu-eval \n\ + rvpush mu-eval-return \n\ + rvpush py-eval \n\ + rvpush py-eval-return \n\ + rvpush py-exec \n\ + rvpush url rvlink://\n\ + \n\ + Examples:\n\ + \n\ + To set the media contents of the currently running RV:\n\ + rvpush set [ foo.mov -in 101 -out 120 ] \n\ + \n\ + To add to the media contents of the currently running RV with tag \"myrv\":\n\ + rvpush -tag myrv merge [ fooLeft.mov fooRight.mov ] \n\ + \n\ + To execute arbitrary Mu code in the currently running RV:\n\ + rvpush mu-eval 'play()'\n\ + \n\ + To execute arbitrary Mu code in the currently running RV, and print the result:\n\ + rvpush mu-eval-return 'frame()'\n\ + \n\ + To evaluate an arbitrary Python expression in the currently running RV:\n\ + rvpush py-eval 'rv.commands.play()'\n\ + \n\ + To evaluate an arbitrary Python expression in the currently running RV, and print the result:\n\ + rvpush py-eval-return 'rv.commands.frame()'\n\ + \n\ + To execute arbitrary Python statements in the currently running RV:\n\ + rvpush py-exec 'from rv import commands; commands.play()'\n\ + \n\ + To process an rvlink url in the currently running RV, loading a movie into the current session:\n\ + rvpush url 'rvlink:// -reuse 1 foo.mov'\n\ + \n\ + To process an rvlink url in the currently running RV, loading a movie into a new session:\n\ + rvpush url 'rvlink:// -reuse 0 foo.mov'\n\ + \n\ + Set environment variable RVPUSH_RV_EXECUTABLE_PATH if you want rvpush to \n\ + start something other than the default RV when it cannot find a running \n\ + RV. Set to 'none' if you want no RV to be started. \n\ + \n\ + Exit status: \n\ + 4: Connection to running RV failed \n\ + 11: Could not connect to running RV, and could not start new RV \n\ + 15: Cound not connect to running RV, started new one. \n\ +"; + +#if 0 +#define DB(x) cerr << dec << x << endl +#define DBL(level, x) cerr << dec << x << endl +#else +#define DB(x) +#define DBL(level, x) +#endif + +int utf8Main(int argc, char *argv[]) +{ + if (argc < 2 || strcmp (argv[1],"-help") == 0) + { + cerr << usage << endl; + exit (1); + } + + int commandPos = 1; + string tag; + DB ("argv1 '" << argv[1] << "'"); + if (string(argv[1]) == "-tag") + { + if (argc < 3) + { + cerr << "ERROR: missing tag" << endl; + cerr << usage << endl; + exit (2); + } + tag = argv[2]; + commandPos = 3; + } + string command = argv[commandPos]; + DB ("commandPos " << commandPos << " command " << command); + + if (command != "set" && + command != "merge" && + command != "mu-eval" && + command != "mu-eval-return" && + command != "py-eval" && + command != "py-eval-return" && + command != "py-exec" && + command != "url") + { + cerr << usage << endl; + exit (3); + } + + vector commandArgv; + if (command == "url") + { + string url; + + for (int i = commandPos+1; i < argc; ++i) + { + if (i > commandPos+1) url += " "; + url += argv[i]; + } + commandArgv.push_back (url); + } + else for (int i = commandPos+1; i < argc; ++i) commandArgv.push_back (argv[i]); + DB ("command " << command << " tag " << tag); + + QCoreApplication app(argc, argv); + + RvPusher pusher(app, tag, command, commandArgv); + + // + // Start the app event loop only if we neither got an error constructing + // the RvPusher nor started a new RV (IE we're going to communicate with a + // running RV over the network). + // + + if (pusher.error() == 0 && !pusher.rvStarted()) app.exec(); + + DB ("exiting, return " << pusher.error()); + + exit (pusher.error()); +} diff --git a/src/bin/apps/rvpush/rvpush.wrapper b/src/bin/apps/rvpush/rvpush.wrapper new file mode 100755 index 000000000..6cc8cb900 --- /dev/null +++ b/src/bin/apps/rvpush/rvpush.wrapper @@ -0,0 +1,50 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the APP_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set APP_HOME if invoked from a link to the script +# and APP_HOME is not already set +# + +set noglob +set name = rvpush + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +echo RV_HOME = $RV_HOME +set platform = i386 +set bin = "$RV_HOME/bin/$name.bin.$platform" + +if (! (-e $bin) ) then + set bin = "$RV_HOME/bin/$name.bin" +endif + +if (! (-e $bin) ) then + echo "ERROR: binary " $bin " not found" + exit(-1) +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec binary + +exec $bin $*:q diff --git a/src/bin/apps/rvpush/utf8Main.cpp b/src/bin/apps/rvpush/utf8Main.cpp new file mode 100644 index 000000000..69e097cc9 --- /dev/null +++ b/src/bin/apps/rvpush/utf8Main.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.cpp" \ No newline at end of file diff --git a/src/bin/apps/rvshell/CMakeLists.txt b/src/bin/apps/rvshell/CMakeLists.txt new file mode 100644 index 000000000..5f6a23dcc --- /dev/null +++ b/src/bin/apps/rvshell/CMakeLists.txt @@ -0,0 +1,54 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "rvshell" +) + +SET(CMAKE_AUTOUIC + ON +) +SET(CMAKE_AUTOMOC + ON +) +SET(CMAKE_AUTORCC + ON +) + +SET(_sources + main.cpp utf8Main.cpp ShellDialog.cpp +) + +FIND_PACKAGE( + Qt5 + COMPONENTS Core Gui Network Widgets + REQUIRED +) + +QT5_WRAP_UI(_qt_ui_headers ShellDialog.ui) + +ADD_EXECUTABLE( + ${_target} + ${_sources} ${_qt_ui_headers} +) +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE TwkQtChat Qt5::Core Qt5::Gui Qt5::Network Qt5::Widgets +) + +RV_STAGE(TYPE "EXECUTABLE_WITH_PLUGINS" TARGET ${_target}) diff --git a/src/bin/apps/rvshell/ShellDialog.cpp b/src/bin/apps/rvshell/ShellDialog.cpp new file mode 100644 index 000000000..b1cf278c7 --- /dev/null +++ b/src/bin/apps/rvshell/ShellDialog.cpp @@ -0,0 +1,471 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include +#include +#include "ShellDialog.h" +#include +#include + +using namespace TwkQtChat; +using namespace std; + +static bool updateOnly = false; + +ShellDialog::ShellDialog(QWidget *parent) : QMainWindow(parent) +{ + if (getenv("RVSHELL_PIXEL_UPDATE_ONLY")) updateOnly = true; + + setupUi(this); + // + // Last arg below turns off the ping-pong "heartbeat" from rv + // + m_client = new Client("rv-shell-1", "rv-shell", 0, false); + m_imageData = 0; + m_stop = false; + + plainTextEdit->setFocusPolicy(Qt::StrongFocus); + textEdit->setFocusPolicy(Qt::NoFocus); + textEdit->setReadOnly(true); + listWidget->setFocusPolicy(Qt::NoFocus); + + m_scene = new QGraphicsScene(this); + m_pixmapItem = 0; + graphicsView->setScene(m_scene); + + connect(actionSend, SIGNAL(triggered()), this, SLOT(go())); + connect(actionQuit, SIGNAL(triggered()), + this, SLOT(quit())); + + connect(m_client, SIGNAL(newMessage(const QString &, const QString &)), + this, SLOT(appendMessage(const QString &, const QString &))); + connect(m_client, SIGNAL(newContact(const QString &)), + this, SLOT(newContact(const QString &))); + connect(m_client, SIGNAL(contactLeft(const QString &)), + this, SLOT(contactLeft(const QString &))); + + connect(sendImageButton, SIGNAL(clicked()), this, SLOT(sendImage())); + + connect(playButton, SIGNAL(clicked()), this, SLOT(play())); + connect(stopButton, SIGNAL(clicked()), this, SLOT(stop())); + connect(forwardButton, SIGNAL(clicked()), this, SLOT(forward())); + connect(backButton, SIGNAL(clicked()), this, SLOT(back())); + connect(fullScreenButton, SIGNAL(clicked(bool)), this, SLOT(fullScreen(bool))); + connect(cacheButton, SIGNAL(clicked(bool)), this, SLOT(cacheOn(bool))); + connect(loadImageButton, SIGNAL(clicked()), this, SLOT(loadImage())); + + m_tableFormat.setBorder(0); + //QTimer::singleShot(10 * 1000, this, SLOT(showInformation())); + + xTilesLineEdit->setText("1"); + yTilesLineEdit->setText("1"); +} + +void ShellDialog::appendMessage(const QString &from, const QString &message) +{ + if (from.isEmpty() || message.isEmpty()) + return; + + QTextCursor cursor(textEdit->textCursor()); + cursor.movePosition(QTextCursor::End); + QTextTable *table = cursor.insertTable(1, 2, m_tableFormat); + table->cellAt(0, 0).firstCursorPosition().insertText("<" + from + "> "); + table->cellAt(0, 1).firstCursorPosition().insertText(message); + QScrollBar *bar = textEdit->verticalScrollBar(); + bar->setValue(bar->maximum()); +} + +void ShellDialog::go() +{ + QString text = plainTextEdit->toPlainText(); + QString event = eventNameLineEdit->text(); + if (text.isEmpty()) return; + m_client->sendEvent(m_contact, event, eventTargetLineEdit->text(), text, true); + //plainTextEdit->clear(); +} + +void ShellDialog::newContact(const QString &contact) +{ + if (contact.isEmpty()) return; + + QColor color = textEdit->textColor(); + textEdit->setTextColor(Qt::gray); + textEdit->append(tr("Connected to %1").arg(contact)); + textEdit->setTextColor(color); + listWidget->addItem(contact); + m_contact = contact; +} + +void ShellDialog::contactLeft(const QString& contact) +{ + if (contact.isEmpty()) return; + + QList items = listWidget->findItems(contact, + Qt::MatchExactly); + if (items.isEmpty()) return; + delete items.at(0); + + QColor color = textEdit->textColor(); + textEdit->setTextColor(Qt::gray); + textEdit->append(tr("No longer connected to %1").arg(contact)); + textEdit->setTextColor(color); + + m_stop = true; +} + +void ShellDialog::play() +{ + m_client->sendEvent(m_contact, + "remote-eval", + eventTargetLineEdit->text(), + "play();" + "\"FRAME = \" + frame();", + true); +} + +void ShellDialog::stop() +{ + m_client->sendEvent(m_contact, + "remote-eval", + eventTargetLineEdit->text(), + "stop();" + "\"FRAME = \" + frame();", + true); +} + +void ShellDialog::forward() +{ + m_client->sendEvent(m_contact, + "remote-eval", + eventTargetLineEdit->text(), + "extra_commands.stepForward1();" + "\"FRAME = \" + frame();", + true); +} + +void ShellDialog::back() +{ + m_client->sendEvent(m_contact, + "remote-eval", + eventTargetLineEdit->text(), + "extra_commands.stepBackward1();" + "\"FRAME = \" + frame();", + true); +} + +void ShellDialog::fullScreen(bool b) +{ + QString msg = QString("fullScreenMode(%1)").arg(b ? "true" : "false"); + m_client->sendEvent(m_contact, + "remote-eval", + eventTargetLineEdit->text(), + msg); +} + +void ShellDialog::cacheOn(bool b) +{ + QString msg = QString("setCacheMode(%1)").arg(b ? "CacheBuffer" : "CacheOff"); + m_client->sendEvent(m_contact, + "remote-eval", + eventTargetLineEdit->text(), + msg); +} + +void ShellDialog::loadImage() +{ + QString file = + QFileDialog::getOpenFileName(this, "Select Image", + ".", "Images (*.png *.xpm *.jpg)"); + + if (file == "") return; + + if (!m_pixmap.load(file)) + { + QMessageBox::critical(this, "Image not Read", + "The image could not be read"); + return; + } + + delete [] m_imageData; + m_imageData = 0; + + if (m_pixmapItem) m_scene->removeItem(m_pixmapItem); + m_pixmapItem = m_scene->addPixmap(m_pixmap); + + QImage image = m_pixmap.toImage(); + + const size_t w = image.width(); + const size_t h = image.height(); + const size_t scanlineSize = w * 4; + m_imageData = new unsigned char [w * h * 4]; + + for (size_t y=0; y < h; y++) + { + unsigned char* scanline = m_imageData + scanlineSize * y; + + for (size_t x=0; x < w; x++) + { + QRgb p = image.pixel(int(x), int(h - y - 1)); + scanline[x * 4 + 0] = qRed(p); + scanline[x * 4 + 1] = qGreen(p); + scanline[x * 4 + 2] = qBlue(p); + scanline[x * 4 + 3] = qAlpha(p); + } + } + + // + // Each load creates a new image source in the remote rv. This + // creates a name based on the current time and date. This + // ensures that each time we load a new image we end up creating + // a new unique source on the RV side for it. Otherwise, we end + // up send pixel buckets to the same source over and over. + // + + m_mediaName = QTime::currentTime().toString( + QDate::currentDate().toString("yyyy-M-d-h:m:s:z")); +} + +void ShellDialog::sendImage() +{ + // + // This is pointless if there's no image data + // + + if (!m_imageData) return; + + static bool first = true; + + // + // Number of tiles the user specified in the UI + // + + const int nx = xTilesLineEdit->text().toInt(); + const int ny = yTilesLineEdit->text().toInt(); + + // + // Reset the stop variable. This may be set by contactLeft() in + // the case that we're hung up on. + // + + m_stop = false; + + // + // Copy the QImage it into something we know the layout of + // + + const size_t w = m_pixmap.width(); + const size_t h = m_pixmap.height(); + const size_t scanlineSize = w * 4; + unsigned char* data = m_imageData; + + // + // NOTE: don't get confused by the order in which we built up the + // command string the next few lines of code. We're trying to + // send a single command to RV to create a unqiue image source + // that has space allocated for a left and right image. Since we + // don't really care what the source name is on this end (just + // the "media name" that we created and which identifies where + // the pixels should go) we create a temporary variable "s" which + // holds the source name. The source name is required to create + // new source pixels. By doing everything in one little code + // snippet we avoid having to get the source name and use it in + // subsequent calls to newImageSourcePixels(). This can be taken + // to an extreme and entire Mu modules with thousands of lines of + // code can be uploaded to RV like this. + // + + // + // The newImageSource() function needs to be called for RV create + // space for the image. After this the pixel blocks will refer to + // the media name. The media name -- which we created when the + // image was loaded in loadImage() above -- identifies where the + // pixels will go. newImageSource() will return the name of the + // new source, but we'll only need that to setup the source + // pixels not when we send tiles. + // + + if (first || !updateOnly) + { + first = false; + QString newImageSource = + QString("newImageSource( \"%1\", %2, %3, " // name, w, h + "%2, %3, 0, 0, " // uncrop w, h, x-off, y-off, + "1.0, 4, 8, false, " // pixaspect, 4 ch, 8 bit, nofloat + "1, 1, 24.0, " // fs, fe, fps + "nil, " // layers (none in this case) + "string[] {\"left\", \"right\"} )") // views + .arg(m_mediaName) + .arg(w) + .arg(h); + + // + // The "s" argument below will be the result of newImageSource() + // (which we constructed above) when we put this all + // together. For this example, we're sending stereo images (which + // is image the user chose and its inverse). + // + + QString leftPixels = "newImageSourcePixels( s, 1, nil, \"left\")"; + QString rightPixels = "newImageSourcePixels( s, 1, nil, \"right\")"; + + // + // Put together the newImageSource() and newImageSourcePixels() + // calls. Make a Mu lexical block and call the two + // newImageSourcePixels() calls with the result of + // newImageSource(). So the final string will look like this: + // + // { + // let s = newImageSource(...); + // newImageSourcePixels(s, ..., "left"); + // newImageSourcePixels(s, ..., "right"); + // } + // + // The entire thing will be evaluated in one go on the RV end. + // + + QString msg = QString("{ let s = %1; %2; %3; }") + .arg(newImageSource) + .arg(leftPixels) + .arg(rightPixels); + + // + // Send the command + // + + m_client->sendEvent(m_contact, + "remote-eval", + "*", + msg.toUtf8().data(), + true); + + m_client->waitForSend(m_contact); + + if (!m_client->waitForMessage(m_contact)) + { + QMessageBox::critical(this, "Timeout", + QString("%1 failed to respond in time").arg(m_contact)); + return; + } + } + + + const unsigned char* d = m_imageData; + size_t iw = w; + size_t ih = h; + + // + // Loop over the tiles. If m_stop ever becomes true break out + // of the loops (nobody is listening). + // + + for (size_t tx = 0; !m_stop && tx < nx; tx++) + { + size_t tw = iw / nx; + size_t ix = tw * tx; + if (tx == (nx - 1)) tw = iw - tw * tx; // pick up the leftover + + for (size_t ty = 0; !m_stop && ty < ny; ty++) + { + size_t th = ih / ny; + size_t iy = th * ty; + if (ty == (ny - 1)) th = ih - th * ty; // pick up the leftover + +#if 0 + cout << "SENDING TILE (" << tx << ", " << ty << ")" + << " -- tw = " << tw << ", th = " << th + << endl; +#endif + + QByteArray tile; +#ifdef PLATFORM_WINDOWS + int ro = (updateOnly) ? (rand() % 128) : 0; +#else + int ro = (updateOnly) ? (random() % 128) : 0; +#endif + + for (size_t tr = 0; tr < th; tr++) + { + for (size_t i = 0; i < tw * 4; i++) + { + tile.push_back(ro + *(d + iy * (iw * 4) + + tr * (iw * 4) + + (ix * 4) + i)); + } + } + + if (tile.size() != (tw * th * 4)) + { + cout << "bad tiles" << endl; + cout << "tile.size() = " << tile.size() << endl; + cout << "tw * th * 4 = " << (tw * th * 4) << endl; + } + + // + // Create the interp string. For RV the PIXELTILE looks + // like a python function call with keyword args. There + // can be no spaces in the string. The following arg + // variables are available, if they are not specified + // they get the defaults: + // + // event string, default = "pixel-block" + // tag (name) unique identifier for this image + // media (name) string, default = "" + // layer (name) string, default = "" + // view (name) string, default = "" + // w (width) value > 0, no default (required) + // h (height) value > 0, no default (required) + // x (x tile pos) any int, default is 0 + // y (y tile pos) any int, default is 0 + // f (frame) any int, default is 1 + // + + QString interpLeft = QString( "PIXELTILE(" + "media=%1,view=left,w=%2,h=%3,x=%4,y=%5,f=1)") + .arg(m_mediaName).arg(tw).arg(th).arg(ix).arg(iy); + + QString interpRight = QString( "PIXELTILE(" + "media=%1,view=right,w=%2,h=%3,x=%4,y=%5,f=1)") + .arg(m_mediaName).arg(tw).arg(th).arg(ix).arg(iy); + + m_client->sendData(m_contact, interpLeft, tile); + m_client->waitForSend(m_contact); + + // + // Make an inverted tile for the other eye + // + + for (int i=0; i < tile.size(); i++) + { + tile[i] = 255 - tile[i]; + } + + m_client->sendData(m_contact, interpRight, tile); + m_client->waitForSend(m_contact); + + // + // If we don't process events, the network lib will + // eventually timeout because it may not respond to + // ping" packets sent to verify the connection. You can + // use other methods, like creating a QEventLoop which + // will accomplish the same goal. + // + // Also, if the RV we're talking to suddenly hangs up + // we'll need to know about it. The contactLeft() slot + // will be called during event processing causing the + // m_stop member variable to be set to true. + // + + QCoreApplication::instance()->processEvents(); + } + } +} + +void +ShellDialog::quit() +{ + m_client->signOff (m_contact); + qApp->quit(); +} + diff --git a/src/bin/apps/rvshell/ShellDialog.h b/src/bin/apps/rvshell/ShellDialog.h new file mode 100644 index 000000000..1f0a8d20a --- /dev/null +++ b/src/bin/apps/rvshell/ShellDialog.h @@ -0,0 +1,53 @@ +// +// Copyright (c) 2008 Tweak Software. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +#ifndef __rvshell__ShellDialog__h__ +#define __rvshell__ShellDialog__h__ +#include +#include "ui_ShellDialog.h" +#include + +class ShellDialog : public QMainWindow, private Ui::ShellDialog +{ + Q_OBJECT + +public: + ShellDialog(QWidget *parent = 0); + TwkQtChat::Client* client() const { return m_client; } + +public slots: + void appendMessage(const QString &from, const QString &message); + +private slots: + void go(); + void newContact(const QString&); + void contactLeft(const QString&); + void loadImage(); + + void play(); + void stop(); + void forward(); + void back(); + void fullScreen(bool); + void cacheOn(bool); + void quit(); + + void sendImage(); + +private: + TwkQtChat::Client* m_client; + QTextTableFormat m_tableFormat; + QGraphicsScene* m_scene; + QPixmap m_pixmap; + QGraphicsPixmapItem* m_pixmapItem; + QString m_contact; + QString m_mediaName; + unsigned char* m_imageData; + bool m_stop; +}; + +#endif // __rvshell__ShellDialog__h__ diff --git a/src/bin/apps/rvshell/ShellDialog.ui b/src/bin/apps/rvshell/ShellDialog.ui new file mode 100644 index 000000000..76a6823f0 --- /dev/null +++ b/src/bin/apps/rvshell/ShellDialog.ui @@ -0,0 +1,579 @@ + + + ShellDialog + + + + 0 + 0 + 734 + 707 + + + + MainWindow + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + Qt::Vertical + + + + true + + + + 0 + 0 + + + + QTabWidget::Rounded + + + 0 + + + + Playback Control + + + + + + + 0 + 0 + + + + Play Controls + + + + + + + 0 + 60 + + + + Back +Frame + + + + + + + + 0 + 60 + + + + Play + + + + + + + + 0 + 60 + + + + Stop + + + + + + + + 0 + 60 + + + + Forward +Frame + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 60 + + + + Cache + + + true + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 60 + + + + Full +Screen + + + true + + + false + + + + + + + + + + + Raw Event + + + + + + + 0 + 0 + + + + Event + + + + + + + Monaco + 10 + true + + + + IBeamCursor + + + play() + + + 8 + + + + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + + QLayout::SetMinimumSize + + + + + Name: + + + + + + + + 0 + 0 + + + + + 150 + 16777215 + + + + + Monospace + + + + remote-eval + + + + + + + Target + + + + + + + + 0 + 0 + + + + + 150 + 16777215 + + + + + Monospace + + + + * + + + + + + + + + + Send + + + + + + + + + + + Pixels + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Load Image... + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + X Tiles + + + + + + + + 50 + 16777215 + + + + 1 + + + + + + + Y Tiles + + + + + + + + 50 + 16777215 + + + + 1 + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 40 + 20 + + + + + + + + Send Image + + + + + + + + + + + + + 0 + 0 + + + + + + + + 0 + 0 + + + + Feedback + + + Qt::AlignCenter + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + 0 + 0 + + + + + 0 + 300 + + + + + Monaco + 10 + true + + + + IBeamCursor + + + Qt::NoFocus + + + true + + + + + + 180 + 16777215 + + + + + Monaco + 8 + true + + + + IBeamCursor + + + Qt::NoFocus + + + + + + + + + + + + + + 0 + 0 + 734 + 22 + + + + + File + + + + + + + + + + + Send + + + + + Open... + + + + + Quit + + + + + + + pushButton + clicked() + actionSend + trigger() + + + 516 + 113 + + + -1 + -1 + + + + + diff --git a/src/bin/apps/rvshell/main.cpp b/src/bin/apps/rvshell/main.cpp new file mode 100644 index 000000000..a2e9bbc1e --- /dev/null +++ b/src/bin/apps/rvshell/main.cpp @@ -0,0 +1,60 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +#include "../../utf8Main.h" + +#include +#include +#include +#include +#include + +#include "ShellDialog.h" + +using namespace std; + +int utf8Main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + ShellDialog dialog; + + if (argc >= 3) + { + cout << endl; + QHostInfo info = QHostInfo::fromName(argv[2]); + int port = 45124; + if (argc == 4) port = atoi(argv[3]); + QHostAddress addr; + + for (int i=0; i < info.addresses().size(); i++) + { + if (info.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) + { + addr = info.addresses()[i]; + cout << "found: " << info.addresses()[i].toString().toUtf8().data() << endl; + break; + } + } + + cout << "Connecting to " + << argv[1] + << " at " + << addr.toString().toUtf8().data() + << ":" << port + << endl; + + dialog.client()->connectTo(argv[1], addr, port); + } + else + { + cerr << "usage: rvshell [port]" << endl; + exit (1); + } + + dialog.show(); + dialog.raise(); + return app.exec(); +} diff --git a/src/bin/apps/rvshell/rvshell.wrapper b/src/bin/apps/rvshell/rvshell.wrapper new file mode 100755 index 000000000..2bc09332e --- /dev/null +++ b/src/bin/apps/rvshell/rvshell.wrapper @@ -0,0 +1,50 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the APP_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set APP_HOME if invoked from a link to the script +# and APP_HOME is not already set +# + +set noglob +set name = rvshell + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +echo RV_HOME = $RV_HOME +set platform = i386 +set bin = "$RV_HOME/bin/$name.bin.$platform" + +if (! (-e $bin) ) then + set bin = "$RV_HOME/bin/$name.bin" +endif + +if (! (-e $bin) ) then + echo "ERROR: binary " $bin " not found" + exit(-1) +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec binary + +exec $bin $*:q diff --git a/src/bin/apps/rvshell/utf8Main.cpp b/src/bin/apps/rvshell/utf8Main.cpp new file mode 100644 index 000000000..69e097cc9 --- /dev/null +++ b/src/bin/apps/rvshell/utf8Main.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.cpp" \ No newline at end of file diff --git a/src/bin/apps/utf8Main.cpp b/src/bin/apps/utf8Main.cpp new file mode 100644 index 000000000..72cb3b7c2 --- /dev/null +++ b/src/bin/apps/utf8Main.cpp @@ -0,0 +1,49 @@ +//*****************************************************************************/ +// Copyright (c) 2018 Autodesk, Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//*****************************************************************************/ + +#if defined( _WIN32 ) +#include +#endif + + +int utf8Main( int argc, char* argv[] ); + +#if defined( _WIN32 ) +int wmain(int argc, wchar_t* wargv[]) +{ + // converts UCS2 arguements to utf-8 charset + char ** argv = new char * [argc]; + for (int argn = 0; argn < argc; argn++) + { + int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, wargv[argn], -1, NULL, 0, NULL, NULL); + if (sizeNeeded > 0 ) + { + char * strTo = new char [sizeNeeded]; + WideCharToMultiByte(CP_UTF8, 0, wargv[argn], -1, strTo, sizeNeeded, NULL, NULL); + argv[argn] = strTo; + } + else + argv[argn] = nullptr; + } + + int result = utf8Main(argc, argv); + + for (int argn = 0; argn < argc; argn++) + delete [] argv[argn]; + + delete [] argv; + + return result; +} +#else +int main(int argc, char* argv[]) +{ + return utf8Main(argc, argv); +} +#endif + diff --git a/src/bin/apps/utf8Main.h b/src/bin/apps/utf8Main.h new file mode 100644 index 000000000..31f4a4b09 --- /dev/null +++ b/src/bin/apps/utf8Main.h @@ -0,0 +1,9 @@ +//*****************************************************************************/ +// Copyright (c) 2018 Autodesk, Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//*****************************************************************************/ + +int utf8Main( int argc, char* argv[] ); diff --git a/src/bin/gtotools/CMakeLists.txt b/src/bin/gtotools/CMakeLists.txt new file mode 100644 index 000000000..a921f5a1f --- /dev/null +++ b/src/bin/gtotools/CMakeLists.txt @@ -0,0 +1,9 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +ADD_SUBDIRECTORY(gtoimage) +ADD_SUBDIRECTORY(gtoinfo) +ADD_SUBDIRECTORY(gtofilter) +ADD_SUBDIRECTORY(gtomerge) diff --git a/src/bin/gtotools/gtofilter/CMakeLists.txt b/src/bin/gtotools/gtofilter/CMakeLists.txt new file mode 100644 index 000000000..6a26e0fe0 --- /dev/null +++ b/src/bin/gtotools/gtofilter/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "gtofilter" +) + +LIST(APPEND _sources main.cpp utf8Main.cpp) + +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE Gto +) + +IF(RV_TARGET_WINDOWS) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE pcre win_posix + ) +ENDIF() + +RV_STAGE(TYPE "EXECUTABLE" TARGET ${_target}) diff --git a/src/bin/gtotools/gtofilter/gtofilter.wrapper b/src/bin/gtotools/gtofilter/gtofilter.wrapper new file mode 100755 index 000000000..8e5d333df --- /dev/null +++ b/src/bin/gtotools/gtofilter/gtofilter.wrapper @@ -0,0 +1,51 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the APP_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set APP_HOME if invoked from a link to the script +# and APP_HOME is not already set +# + + +set noglob +set name = gtofilter + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +echo RV_HOME = $RV_HOME +set platform = i386 +set bin = "$RV_HOME/bin/$name.bin.$platform" + +if (! (-e $bin) ) then + set bin = "$RV_HOME/bin/$name.bin" +endif + +if (! (-e $bin) ) then + echo "ERROR: binary " $bin " not found" + exit(-1) +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec binary + +exec $bin $*:q diff --git a/src/bin/gtotools/gtofilter/main.cpp b/src/bin/gtotools/gtofilter/main.cpp new file mode 100644 index 000000000..f07a0ee90 --- /dev/null +++ b/src/bin/gtotools/gtofilter/main.cpp @@ -0,0 +1,344 @@ +//****************************************************************************** +// Copyright (c) 2002 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0’ +// +//****************************************************************************** + +#include "../../utf8Main.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#else +#include +#endif + +using namespace Gto; +using namespace std; + +bool verbose = false; +bool glob = true; +bool text = false; +bool nocompress = false; + +regex_t excludeRegex; +regex_t includeRegex; + +struct FullProperty +{ + string name; + string interp; + Object* object; + Component* component; + Components* componentList; + Property* property; +}; + +typedef vector FullProperties; + +struct Empty_p +{ + bool operator() (Object* o) { return o->components.empty(); } +}; + +void gatherComponent(Object* o, Components& components, FullProperties& all) +{ + for (size_t j=0; j < components.size(); j++) + { + Component* c = components[j]; + + for (size_t q=0; q < c->properties.size(); q++) + { + Property* p = c->properties[q]; + + ostringstream fullname; + fullname << o->name << "." << p->fullName; + + FullProperty fp; + fp.name = fullname.str(); + fp.object = o; + fp.component = c; + fp.componentList = &components; + fp.property = p; + fp.interp = p->interp; + + all.push_back(fp); + } + + gatherComponent(o, c->components, all); + } +} + +void gather(RawDataBase* db, FullProperties& all) +{ + for (size_t i=0; i < db->objects.size(); i++) + { + Object* o = db->objects[i]; + gatherComponent(o, o->components, all); + } +} + +void filter(RawDataBase* db, + FullProperties& properties, + const char *include, + const char *exclude) +{ + for (size_t i=0; i < properties.size(); i++) + { + const FullProperty &fp = properties[i]; + bool imatch = false; + bool ematch = false; + + if (include) + { + bool matched; + + if (glob) + { + matched = !fnmatch(include, fp.name.c_str(), 0); + } + else + { + matched = !regexec(&includeRegex, fp.name.c_str(), 0, 0, 0); + } + + if (verbose && matched) + { + cout << "gtomerge: include pattern matched " + << fp.name << endl; + } + + if (matched) imatch = true; + } + + if (exclude) + { + bool matched; + + if (glob) + { + matched = !fnmatch(exclude, fp.name.c_str(), 0); + } + else + { + matched = !regexec(&excludeRegex, fp.name.c_str(), 0, 0, 0); + } + + if (verbose && matched) + { + cout << "gtomerge: exclude pattern matched " + << fp.name << endl; + } + + if (matched) ematch = true; + } + + if (include && imatch && exclude && ematch) + { + cout << "gtomerge: including " << fp.name + << " despite matching include and exclude pattern" + << endl; + } + else if ((include && !exclude && !imatch) || + (!include && exclude && ematch) || + (include && !imatch && exclude && ematch)) + { + Object *o = fp.object; + Component *c = fp.component; + Property *p = fp.property; + + c->properties.erase( find(c->properties.begin(), + c->properties.end(), + p) ); + + if (c->properties.empty()) + { + fp.componentList->erase( find(fp.componentList->begin(), + fp.componentList->end(), + c) ); + + if (fp.object->components.empty()) + { + db->objects.erase( find(db->objects.begin(), + db->objects.end(), + o) ); + } + } + } + } + + // + // Do a clean up pass to handle "empty" input data as well + // + + size_t n = db->objects.size(); + + db->objects.erase(remove_if(db->objects.begin(), + db->objects.end(), + Empty_p()), + db->objects.end()); + + if (verbose && n != db->objects.size()) + { + cout << "gtofilter: removed " << (n - db->objects.size()) + << " empty objects from input" + << endl; + } +} + +void usage() +{ + cout << "USAGE: " + << "gtofilter [options] -o outfile.gto infile.gto\n" + << "-nc force uncompressed file output\n" + << "-t output text format\n" + << "-regex use basic posix regular expressions\n" + << "-glob use glob regular expressions (default)\n" + << "-ie/--include regex inclusion regex\n" + << "-ee/--exclude regex exclusion regex\n" + << "-v verbose\n" + << endl; + + exit(-1); +} + +int utf8Main(int argc, char *argv[]) +{ + char *inFile = 0; + char *outFile = 0; + char *includeExpr = 0; + char *excludeExpr = 0; + + for (int i=1; i < argc; i++) + { + if (!strcmp(argv[i], "-o")) + { + i++; + if (i >= argc) usage(); + outFile = argv[i]; + } + else if (!strcmp(argv[i], "-ie") || !strcmp(argv[i], "--include")) + { + i++; + if (i >= argc) usage(); + includeExpr = argv[i]; + } + else if (!strcmp(argv[i], "-ee") || !strcmp(argv[i], "--exclude")) + { + i++; + if (i >= argc) usage(); + excludeExpr = argv[i]; + } + else if (!strcmp(argv[i], "-v")) + { + verbose = true; + } + else if (!strcmp(argv[i], "-nc")) + { + nocompress = true; + } + else if (!strcmp(argv[i], "-t")) + { + text = true; + } + else if (!strcmp(argv[i], "-glob")) + { + glob = true; + } + else if (!strcmp(argv[i], "-regex")) + { + glob = false; + } + else if (*argv[i] == '-') + { + usage(); + } + else + { + if (inFile) usage(); + inFile = argv[i]; + } + } + + if (!inFile || !outFile) + { + cout << "no infile or outfile specified.\n" << flush; + cout << endl; + usage(); + } + + if (!glob) + { + if (excludeExpr) + { + if (int err = regcomp(&excludeRegex, excludeExpr, REG_NOSUB)) + { + char temp[256]; + regerror(err, &excludeRegex, temp, 256); + cerr << "ERROR: " << temp << endl; + exit(-1); + } + } + + if (includeExpr) + { + if (int err = regcomp(&includeRegex, includeExpr, REG_NOSUB)) + { + char temp[256]; + regerror(err, &includeRegex, temp, 256); + cerr << "ERROR: " << temp << endl; + exit(-1); + } + } + } + + RawDataBaseReader reader; + cout << "Reading input file " << inFile << "..." << endl; + + if (!reader.open(inFile)) + { + cerr << "ERROR: unable to read file " << inFile + << endl; + exit(-1); + } + + RawDataBase *db = reader.dataBase(); + FullProperties allProperties; + gather(db, allProperties); + filter(db, allProperties, includeExpr, excludeExpr); + + if (db->objects.empty()) + { + cerr << "ERROR: everything was excluded" << endl; + exit(-1); + } + + RawDataBaseWriter writer; + Writer::FileType type = Writer::CompressedGTO; + if (nocompress) type = Writer::BinaryGTO; + if (text) type = Writer::TextGTO; + + if (!writer.write(outFile, *db, type)) + { + cerr << "ERROR: unable to write file " << outFile + << endl; + exit(-1); + } + else + { + cout << "Wrote file " << outFile << endl; + } + + return 0; +} diff --git a/src/bin/gtotools/gtofilter/utf8Main.cpp b/src/bin/gtotools/gtofilter/utf8Main.cpp new file mode 100644 index 000000000..69e097cc9 --- /dev/null +++ b/src/bin/gtotools/gtofilter/utf8Main.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.cpp" \ No newline at end of file diff --git a/src/bin/gtotools/gtoimage/CMakeLists.txt b/src/bin/gtotools/gtoimage/CMakeLists.txt new file mode 100644 index 000000000..06ad06fb8 --- /dev/null +++ b/src/bin/gtotools/gtoimage/CMakeLists.txt @@ -0,0 +1,32 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "gtoimage" +) + +SET(_sources + main.cpp utf8Main.cpp +) + +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE Gto tiff +) + +RV_STAGE(TYPE "EXECUTABLE" TARGET ${_target}) diff --git a/src/bin/gtotools/gtoimage/gtoimage.wrapper b/src/bin/gtotools/gtoimage/gtoimage.wrapper new file mode 100755 index 000000000..23a5a499d --- /dev/null +++ b/src/bin/gtotools/gtoimage/gtoimage.wrapper @@ -0,0 +1,50 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the APP_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set APP_HOME if invoked from a link to the script +# and APP_HOME is not already set +# + +set noglob +set name = gtoimage + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +echo RV_HOME = $RV_HOME +set platform = i386 +set bin = "$RV_HOME/bin/$name.bin.$platform" + +if (! (-e $bin) ) then + set bin = "$RV_HOME/bin/$name.bin" +endif + +if (! (-e $bin) ) then + echo "ERROR: binary " $bin " not found" + exit(-1) +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec binary + +exec $bin $*:q diff --git a/src/bin/gtotools/gtoimage/main.cpp b/src/bin/gtotools/gtoimage/main.cpp new file mode 100644 index 000000000..1e881ee35 --- /dev/null +++ b/src/bin/gtotools/gtoimage/main.cpp @@ -0,0 +1,311 @@ +// +// Copyright (c) 2003 Tweak Films +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0’ +// + +#include "../../utf8Main.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace Gto; +using namespace std; + +//---------------------------------------------------------------------- + +string +basename(string path) +{ + size_t lastSlash = path.rfind( "/" ); + + if (lastSlash == path.npos) + { + return path; + } + + return path.substr( lastSlash + 1, path.size() ); +} + + +string +extension(const std::string& path) +{ + string filename(basename(path)); + + if (filename.find( "." ) == filename.npos) + { + return string(""); + } + else + { + return filename.substr( filename.rfind(".") + 1, filename.size() ); + } +} + +string +prefix( string path ) +{ + string filename( basename( path ) ); + if( filename.find( "." ) == filename.npos ) + { + return filename; + } + return filename.substr( 0, filename.find( "." ) ); +} + + +//---------------------------------------------------------------------- + +struct Image +{ + int w; + int h; + short bbs; + int channels; + int type; + size_t size; + size_t pixelSize; + + float* floatPixel(int x, int y) + { + return floatData + y * w * channels + x * channels; + } + + short* shortPixel(int x, int y) + { + return shortData + y * w * channels + x * channels; + } + + char* charPixel(int x, int y) + { + return charData + y * w * channels + x * channels; + } + + union + { + float* floatData; + short* shortData; + char* charData; + void* voidData; + }; +}; + + +Image* +readTIFF(const std::string& imgFileName) +{ + Image* image = new Image; + + // + // Suppress annoying messages about unknown tags, etc... + // + + TIFFSetErrorHandler(0); + TIFFSetWarningHandler(0); + + TIFF* tif = TIFFOpen(imgFileName.c_str(), "r"); + + if (!tif) return 0; + + unsigned short *sampleinfo; + unsigned short extrasamples; + unsigned short bbs; + + TIFFGetField( tif, TIFFTAG_IMAGEWIDTH, &image->w); + TIFFGetField( tif, TIFFTAG_IMAGELENGTH, &image->h); + TIFFGetField( tif, TIFFTAG_BITSPERSAMPLE, &image->bbs ); + + TIFFGetFieldDefaulted( tif, TIFFTAG_EXTRASAMPLES, + &extrasamples, &sampleinfo ); + + image->channels = extrasamples ? 4 : 3; + + switch (image->bbs) + { + case 32: + image->floatData = new float[image->w * image->h * image->channels]; + image->type = Float; + + for (int y = 0; y < image->h; ++y) + { + TIFFReadScanline(tif, image->floatPixel(0, image->h - y - 1), y); + } + break; + + case 16: + image->shortData = new short[image->w * image->h * image->channels]; + image->type = Short; + + for (int y = 0; y < image->h; ++y ) + { + TIFFReadScanline(tif, image->shortPixel(0, image->h - y - 1), y); + } + break; + + case 8: + image->charData = new char[image->w * image->h * image->channels]; + image->type = Byte; + + for (int y = 0; y < image->h; ++y ) + { + TIFFReadScanline( tif, image->charPixel(0, image->h - y - 1), y ); + } + break; + } + + TIFFClose(tif); + return image; +} + +//---------------------------------------------------------------------- + +Object* +makeImageObject(Image* image, const char* inFile) +{ + Object* o = new Object(prefix(inFile), GTO_PROTOCOL_IMAGE, 1); + + Components& c = o->components; + c.push_back(new Component(GTO_COMPONENT_IMAGE, 0)); + + Property* p; + + p = new Property("originalFile", "filename", String, 1, 1, true); + p->stringData[0] = inFile; + c[0]->properties.push_back(p); + + p = new Property("originalEncoding", "filetype", String, 1, 1, true); + p->stringData[0] = "TIFF"; + c[0]->properties.push_back(p); + + const char *itype = image->channels == 3 ? "RGB" : "RGBA"; + + p = new Property(GTO_PROPERTY_TYPE, String, 1, 1, true); + p->stringData[0] = itype; + c[0]->properties.push_back(p); + + p = new Property(GTO_PROPERTY_SIZE, Int, 2, 1, true); + p->int32Data[0] = image->w; + p->int32Data[1] = image->h; + c[0]->properties.push_back(p); + + p = new Property(GTO_PROPERTY_PIXELS, itype, (Gto::DataType)image->type, + image->w * image->h, image->channels, false); + p->voidData = image->voidData; + c[0]->properties.push_back(p); + + return o; +} + + +//---------------------------------------------------------------------- + +void +usage() +{ + cout << "gtoimage [OPTIONS] INFILE OUTFILE" << endl + << endl + << "INFILE a tiff file" << endl + << "OUTFILE a gto file" << endl + << "-t text GTO output" << endl + << "-nc uncompressed GTO output" << endl + << endl; + + exit(-1); +} + +//---------------------------------------------------------------------- + +int utf8Main(int argc, char *argv[]) +{ + char* inFile = 0; + char* outFile = 0; + int nocompress = 0; + int text = 0; + + for (int i=1; i < argc; i++) + { + if (!strcmp(argv[i], "-t")) + { + text = 1; + } + else if (!strcmp(argv[i], "-nc")) + { + nocompress = 1; + } + else + { + if (!inFile) inFile = argv[i]; + else if (!outFile) outFile = argv[i]; + else usage(); + } + } + + // + // Check for bad options + // + + if (!inFile || !outFile) + { + cerr << "ERROR: no infile or outfile specified.\n" << endl; + usage(); + } + + string iext = extension(inFile); + string oext = extension(outFile); + + if (iext == oext) + { + cerr << "ERROR: input and output extensions are the same" << endl; + usage(); + } + + // + // In + // + + RawDataBase* db = new RawDataBase; + Image* i = 0; + cout << "INFO: reading " << inFile << endl; + + if (iext == "tif" || iext == "tiff" || iext == "TIF" || iext == "TIFF") + { + i = readTIFF(inFile); + } + else + { + cerr << "ERROR: Unknown image extension: " << iext << endl; + exit(-1); + } + + db->objects.push_back(makeImageObject(i, inFile)); + + // + // Out + // + + cout << "INFO: writing " << outFile << endl; + + if (oext == "gto") + { + RawDataBaseWriter writer; + Writer::FileType type = Writer::CompressedGTO; + if (nocompress) type = Writer::BinaryGTO; + if (text) type = Writer::TextGTO; + + if (!writer.write(outFile, *db, type)) + { + cerr << "ERRROR: writing file " << outFile << endl; + exit(-1); + } + } + + return 0; +} diff --git a/src/bin/gtotools/gtoimage/utf8Main.cpp b/src/bin/gtotools/gtoimage/utf8Main.cpp new file mode 100644 index 000000000..69e097cc9 --- /dev/null +++ b/src/bin/gtotools/gtoimage/utf8Main.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.cpp" \ No newline at end of file diff --git a/src/bin/gtotools/gtoinfo/CMakeLists.txt b/src/bin/gtotools/gtoinfo/CMakeLists.txt new file mode 100644 index 000000000..0957a8ec5 --- /dev/null +++ b/src/bin/gtotools/gtoinfo/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "gtoinfo" +) + +LIST(APPEND _sources main.cpp utf8Main.cpp) + +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE Gto +) + +IF(RV_TARGET_WINDOWS) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE win_posix + ) +ENDIF() + +RV_STAGE(TYPE "EXECUTABLE" TARGET ${_target}) diff --git a/src/bin/gtotools/gtoinfo/gtoinfo.wrapper b/src/bin/gtotools/gtoinfo/gtoinfo.wrapper new file mode 100755 index 000000000..48654c4b9 --- /dev/null +++ b/src/bin/gtotools/gtoinfo/gtoinfo.wrapper @@ -0,0 +1,50 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the APP_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set APP_HOME if invoked from a link to the script +# and APP_HOME is not already set +# + +set noglob +set name = gtoinfo + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +echo RV_HOME = $RV_HOME +set platform = i386 +set bin = "$RV_HOME/bin/$name.bin.$platform" + +if (! (-e $bin) ) then + set bin = "$RV_HOME/bin/$name.bin" +endif + +if (! (-e $bin) ) then + echo "ERROR: binary " $bin " not found" + exit(-1) +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec binary + +exec $bin $*:q diff --git a/src/bin/gtotools/gtoinfo/main.cpp b/src/bin/gtotools/gtoinfo/main.cpp new file mode 100644 index 000000000..a418f102e --- /dev/null +++ b/src/bin/gtotools/gtoinfo/main.cpp @@ -0,0 +1,829 @@ +// +// Copyright (c) 2003 Tweak Films +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0’ +// + + +#include "../../utf8Main.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +bool outputStrings = false; +bool outputData = false; +bool outputHeader = true; +bool formatData = false; +bool readAll = false; +bool numericStrings = false; +bool filtered = false; +bool outputInterp = false; + +typedef set PropertySet; +typedef set ObjectSet; +PropertySet filteredProperties; +ObjectSet filteredObjects; + + +class Reader : public Gto::Reader +{ +public: + Reader(unsigned int mode) + : Gto::Reader(mode), + m_objectName(Gto::uint32(-1)) {} + + Gto::uint32 m_objectName; + std::string m_componentName; + vector m_buffer; + typedef Gto::Reader::Request Request; + + void headerOutput(const Gto::Reader::PropertyInfo&); + void outputPropertyHeader(const Gto::Reader::PropertyInfo &); + void outputStringTable(); + + virtual void descriptionComplete(); + + virtual Request object(const std::string& name, + const std::string& protocol, + unsigned int protocolVersion, + const Gto::Reader::ObjectInfo &header); + + virtual Request component(const std::string& name, + const std::string& interp, + const Gto::Reader::ComponentInfo &header); + + virtual Request property(const std::string& name, + const std::string& interp, + const Gto::Reader::PropertyInfo &header); + + virtual void* data(const PropertyInfo&, size_t bytes); + virtual void dataRead(const PropertyInfo&); + + virtual void data(const PropertyInfo&, + const float*, size_t numItems); + + virtual void data(const PropertyInfo&, + const double*, size_t numItems); + + virtual void data(const PropertyInfo&, + const int*, size_t numItems); + + virtual void data(const PropertyInfo&, + const unsigned short*, size_t numItems); + + virtual void data(const PropertyInfo&, + const unsigned char*, size_t numItems); + + virtual void data(const PropertyInfo&, bool); + +}; + + +void +Reader::headerOutput(const Gto::Reader::PropertyInfo& pinfo) +{ + const Gto::Reader::ComponentInfo* cinfo = pinfo.component; + const Gto::Reader::ObjectInfo* oinfo = cinfo->object; + + if (m_objectName != oinfo->name) + { + // + // Output the object header information + // + + m_objectName = oinfo->name; + m_componentName = ""; + + cout << "object \"" + << stringFromId(m_objectName) + << "\" protocol \"" + << stringFromId(oinfo->protocolName) + << "\" v" + << oinfo->protocolVersion + << endl; + } + + if (m_componentName != cinfo->fullName) + { + // + // Output the component header information + // + + m_componentName = cinfo->fullName; + + cout << " component \"" + << cinfo->fullName + << "\""; + + if (outputInterp && stringFromId(cinfo->interpretation) != "") + { + cout << " interpret as \"" + << stringFromId(cinfo->interpretation) + << "\" "; + } + + cout << endl; + } +} + +void +Reader::descriptionComplete() +{ + union EndianTest + { + unsigned char c[4]; + unsigned int i; + }; + + EndianTest x; + x.i = 1; + bool big = x.c[3] == 1; + + if (outputHeader) + { + cout << "GTO file version " << fileHeader().version + << ", " << fileHeader().numStrings << " strings, "; + + if (fileHeader().magic == Gto::Header::MagicText) + { + cout << "text format\n"; + } + else + { + if (big) + { + if (isSwapped()) cout << "little endian binary\n"; + else cout << "big endian binary\n"; + } + else + { + if (isSwapped()) cout << "big endian binary\n"; + else cout << "little endian binary\n"; + } + } + + for (Gto::Reader::Properties::const_iterator p = properties().begin(); + p != properties().end(); + ++p) + { + outputPropertyHeader(*p); + } + } + + if (outputStrings) outputStringTable(); +} + +void +Reader::outputStringTable() +{ + const StringTable &strings = stringTable(); + + for (size_t i=0; i < strings.size(); i++) + { + if (i && !formatData) cout << ", "; + cout << i << ": \"" << strings[i] << "\""; + if (formatData) cout << endl; + } + + cout << endl; + exit(0); +} + +Reader::Request +Reader::object(const std::string &name, + const std::string &protocol, + unsigned int version, + const Gto::Reader::ObjectInfo &info) +{ + return Request(outputData); +} + +Reader::Request +Reader::component(const std::string &n, + const std::string &i, + const Gto::Reader::ComponentInfo& c) +{ + return Request(outputData); +} + +Reader::Request +Reader::property(const std::string&, + const std::string&, + const Gto::Reader::PropertyInfo &info) +{ + if (!outputData) return Request(false); + + if (filtered) + { + return Request(filteredProperties.find(&info) + != filteredProperties.end()); + } + else + { + return Request(true); + } +} + +void +Reader::outputPropertyHeader(const Gto::Reader::PropertyInfo &info) +{ + headerOutput(info); + + const char* type; + + switch (info.type) + { + case Gto::Int: type = "int"; break; + case Gto::Float: type = "float"; break; + case Gto::Double: type = "double"; break; + case Gto::Half: type = "half"; break; + case Gto::String: type = "string"; break; + case Gto::Short: type = "short"; break; + case Gto::Byte: type = "byte"; break; + case Gto::Boolean: type = "bool"; break; + default: type = "unknown"; break; + } + + cout << " property " << type + << "[" << info.dims.x; + + if (info.dims.y > 0) cout << "," << info.dims.y; + if (info.dims.z > 0) cout << "," << info.dims.z; + if (info.dims.w > 0) cout << "," << info.dims.w; + + cout << "]" + << "[" << info.size << "]" + << " \"" + << stringFromId(info.name) + << "\""; + + if (outputInterp && stringFromId(info.interpretation) != "") + { + cout << " interpret as \"" + << stringFromId(info.interpretation) + << "\" "; + } + + cout << endl; +} + +void* +Reader::data(const PropertyInfo& info, size_t bytes) +{ + if (bytes) + { + m_buffer.resize(bytes); + return &m_buffer.front(); + } + else + { + switch (info.type) + { + case Gto::Float: + data(info, (float*)0, info.size); + break; + case Gto::Double: + data(info, (double*)0, info.size); + break; + case Gto::Int: + case Gto::String: + data(info, (int*)0, info.size); + break; + case Gto::Short: + data(info, (unsigned short*)0, info.size); + break; + case Gto::Byte: + data(info, (unsigned char*)0, info.size); + break; + case Gto::Boolean: + break; + } + } + + return 0; +} + +void +Reader::dataRead(const PropertyInfo& info) +{ + void* buffer = (void*)&m_buffer.front(); + + switch (info.type) + { + case Gto::Float: + data(info, (float*)buffer, info.size); + break; + case Gto::Double: + data(info, (double*)buffer, info.size); + break; + case Gto::Int: + case Gto::String: + data(info, (int*)buffer, info.size); + break; + case Gto::Short: + data(info, (unsigned short*)buffer, info.size); + break; + case Gto::Byte: + data(info, (unsigned char*)buffer, info.size); + break; + case Gto::Boolean: + break; + } +} + +void Reader::data(const PropertyInfo& info, const float* data, size_t numItems) +{ + if (!outputData) return; + + cout << "float[" << info.dims.x; + if (info.dims.y > 0) cout << "," << info.dims.y; + if (info.dims.z > 0) cout << "," << info.dims.z; + if (info.dims.w > 0) cout << "," << info.dims.w; + cout << "] " + << stringFromId(info.component->object->name) + << "." << info.component->fullName + << "." << stringFromId(info.name) + << " = "; + + if (formatData) + { + cout << endl << "[" << endl; + } + else + { + cout << "["; + } + + for (size_t i=0; i < numItems; i++) + { + if (formatData) cout << " "; + + if (info.dims.x > 1 ) + { + size_t esize = Gto::elementSize(info.dims); + cout << " ["; + for( int j = 0; j < esize; ++j ) + { + cout << " " << data[(i*esize)+j]; + } + cout << " ]"; + } + else + { + cout << " " << data[i]; + } + + if (formatData) cout << endl; + } + + cout << (formatData ? "" : " ") << "]" << endl; +} + +void Reader::data(const PropertyInfo& info, const double* data, size_t numItems) +{ + if (!outputData) return; + + cout << "double[" << info.dims.x; + if (info.dims.y > 0) cout << "," << info.dims.y; + if (info.dims.z > 0) cout << "," << info.dims.z; + if (info.dims.w > 0) cout << "," << info.dims.w; + cout << "] " + << stringFromId(info.component->object->name) + << "." << info.component->fullName + << "." << stringFromId(info.name) + << " = "; + + if (formatData) + { + cout << endl << "[" << endl; + } + else + { + cout << "["; + } + + for (size_t i=0; i < numItems; i++) + { + if (formatData) cout << " "; + + size_t esize = Gto::elementSize(info.dims); + + if (esize > 1 ) + { + cout << " ["; + for( int j = 0; j < esize; ++j ) + { + cout << " " << data[(i*esize)+j]; + } + cout << " ]"; + } + else + { + cout << " " << data[i]; + } + + if (formatData) cout << endl; + } + + cout << (formatData ? "" : " ") << "]" << endl; +} + +void Reader::data(const PropertyInfo& info, const int* data, size_t numItems) +{ + if (!outputData) return; + + if (info.type == Gto::Int) + { + cout << "int[" << info.dims.x; + if (info.dims.y > 0) cout << "," << info.dims.y; + if (info.dims.z > 0) cout << "," << info.dims.z; + if (info.dims.w > 0) cout << "," << info.dims.w; + cout << "] " + << stringFromId(info.component->object->name) + << "." << info.component->fullName + << "." << stringFromId(info.name) + << " = "; + + if (formatData) + { + cout << endl << "[" << endl; + } + else + { + cout << "["; + } + + for (size_t i=0; i < numItems; i++) + { + if (formatData) cout << " "; + + size_t esize = Gto::elementSize(info.dims); + + if (esize > 1) + { + cout << " ["; + for( int j = 0; j < esize; ++j ) + { + cout << " " << data[(i*esize)+j]; + } + cout << " ]"; + } + else + { + cout << " " << data[i]; + } + + if (formatData) cout << endl; + } + } + else + { + cout << "string[" + << info.dims.x; + if (info.dims.y > 0) cout << "," << info.dims.y; + if (info.dims.z > 0) cout << "," << info.dims.z; + if (info.dims.w > 0) cout << "," << info.dims.w; + cout << "] " + << stringFromId(info.component->object->name) + << "." << info.component->fullName + << "." << stringFromId(info.name) + << " = "; + + if (formatData) + { + cout << endl << "[" << endl; + } + else + { + cout << "["; + } + + for (size_t i=0; i < numItems; i++) + { + if (formatData) cout << " "; + + size_t esize = Gto::elementSize(info.dims); + + if( esize > 1 ) + { + cout << " ["; + for( int j = 0; j < esize; ++j ) + { + if (numericStrings) + { + cout << " " << data[(i*esize)+j]; + } + else + { + cout << " \"" << stringFromId(data[(i*esize)+j]); + cout << "\""; + } + } + cout << " ]"; + } + else + { + if (numericStrings) + { + cout << " " << data[i]; + } + else + { + cout << " \"" << stringFromId(data[i]) << "\""; + } + } + + if (formatData) cout << endl; + } + } + + cout << (formatData ? "" : " ") << "]" << endl; +} + +void Reader::data(const PropertyInfo& info, + const unsigned short* data, + size_t numItems) +{ + if (!outputData) return; + + if (info.type == Gto::Short) + { + cout << "short[" << info.dims.x; + if (info.dims.y > 0) cout << "," << info.dims.y; + if (info.dims.z > 0) cout << "," << info.dims.z; + if (info.dims.w > 0) cout << "," << info.dims.w; + cout << "] " + << stringFromId(info.component->object->name) + << "." << info.component->fullName + << "." << stringFromId(info.name) + << " = "; + + if (formatData) + { + cout << endl << "[" << endl; + } + else + { + cout << "["; + } + + + for (size_t i=0; i < numItems; i++) + { + if (formatData) cout << " "; + + size_t esize = Gto::elementSize(info.dims); + + if( esize > 1 ) + { + cout << " ["; + for( int j = 0; j < esize; ++j ) + { + cout << " " << data[(i*esize)+j]; + } + cout << " ]"; + } + else + { + cout << " " << data[i]; + } + + if (formatData) cout << endl; + } + } + + cout << (formatData ? "" : " ") << "]" << endl; +} + +void Reader::data(const PropertyInfo& info, + const unsigned char* data, + size_t numItems) +{ + if (!outputData) return; + + if (info.type == Gto::Byte) + { + cout << "byte[" << info.dims.x; + if (info.dims.y > 0) cout << "," << info.dims.y; + if (info.dims.z > 0) cout << "," << info.dims.z; + if (info.dims.w > 0) cout << "," << info.dims.w; + cout << "] " + << stringFromId(info.component->object->name) + << "." << info.component->fullName + << "." << stringFromId(info.name) + << " = "; + + if (formatData) + { + cout << endl << "[" << endl; + } + else + { + cout << "["; + } + + size_t esize = Gto::elementSize(info.dims); + + for (size_t i=0; i < numItems; i++) + { + if (formatData) cout << " "; + + if( esize > 1 ) + { + cout << " ["; + for( int j = 0; j < esize; ++j ) + { + cout << " " << int(data[(i*esize)+j]); + } + cout << " ]"; + } + else + { + cout << " " << int(data[i]); + } + + if (formatData) cout << endl; + } + } + + cout << (formatData ? "" : " ") << "]" << endl; +} + +void Reader::data(const PropertyInfo& info, bool) +{ + if (!outputData) return; +} + +//---------------------------------------------------------------------- + +void filterData(const std::string& filterExpr, Reader& reader) +{ + Gto::Reader::Properties& properties = reader.properties(); + Gto::Reader::Objects& objects = reader.objects(); + + for (size_t i=0; i < properties.size(); i++) + { + Gto::Reader::PropertyInfo& p = properties[i]; + const Gto::Reader::ComponentInfo* c = p.component; + const Gto::Reader::ObjectInfo* o = c->object; + + string name; + name = reader.stringFromId(o->name); + name += "."; + name += reader.stringFromId(c->name); + name += "."; + name += reader.stringFromId(p.name); + + if (!fnmatch(filterExpr.c_str(), name.c_str(), 0)) + { + filteredProperties.insert(&p); + filteredObjects.insert(o); + } + } + + for (size_t i=0; i < objects.size(); i++) + { + Gto::Reader::ObjectInfo& p = objects[i]; + + if (filteredObjects.find(&p) != filteredObjects.end()) + { + reader.accessObject(p); + } + } +} + +//---------------------------------------------------------------------- + +void usage() +{ + cout << "gtoinfo [options] file.gto\n" + << endl + << "-a/--all dump data and header\n" + << "-d/--dump dump data (no header)\n" + << "-l/--line dump data/strings one per line\n" + << "-h/--header header only (default)\n" + << "-s/--strings output strings table\n" + << "-n/--numeric-strings output string properties as string numbers\n" + << "-i/--interpretation-strings output interpretation strings\n" + << "-f/--filter expr filter shell-like expression\n" + << "-r/--readall force data read\n" + << "--help usage\n" + << endl; + + exit(-1); +} + + +int utf8Main(int argc, char *argv[]) +{ + const char *inFile = 0; + string filterExpr; + + for (int i=1; i < argc; i++) + { + const char *arg = argv[i]; + + if (*arg == '-') + { + if (!strcmp(arg, "-d") || + !strcmp(arg, "--dump")) + { + outputData = true; + outputHeader = false; + } + else if (!strcmp(arg, "-a") || + !strcmp(arg, "--all")) + { + outputData = true; + outputHeader = true; + outputInterp = true; + } + else if (!strcmp(arg, "-h") || + !strcmp(arg, "--header")) + { + outputData = false; + outputHeader = true; + } + else if (!strcmp(arg, "-s") || + !strcmp(arg, "--strings")) + { + outputData = false; + outputHeader = false; + outputStrings = true; + } + else if (!strcmp(arg, "-r") || + !strcmp(arg, "--readall")) + { + readAll = true; + } + else if (!strcmp(arg, "-l") || + !strcmp(arg, "--line")) + { + formatData = true; + } + else if (!strcmp(arg, "-n") || + !strcmp(arg, "--numeric-strings")) + { + numericStrings = true; + } + else if (!strcmp(arg, "-i") || + !strcmp(arg, "--interpretation-strings")) + { + outputInterp = true; + } + else if (!strcmp(arg, "-f") || + !strcmp(arg, "--filter-expression")) + { + i++; + filterExpr = argv[i]; + filtered = true; + } + else + { + usage(); + } + } + else + { + inFile = arg; + } + } + + if (!inFile) + { + cout << "no input .gto file specified.\n" << flush; + cout << endl; + usage(); + } + + unsigned int mode = 0; + if (!outputData && !readAll) mode |= Gto::Reader::HeaderOnly; + + if (filterExpr != "") mode = Gto::Reader::RandomAccess; + + Reader reader(mode); + + if ( !reader.open(inFile) ) + { + cerr << "Error reading file " << inFile << endl; + } + + if (filterExpr != "") + { + filterData(filterExpr, reader); + } + + return 0; +} diff --git a/src/bin/gtotools/gtoinfo/utf8Main.cpp b/src/bin/gtotools/gtoinfo/utf8Main.cpp new file mode 100644 index 000000000..69e097cc9 --- /dev/null +++ b/src/bin/gtotools/gtoinfo/utf8Main.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.cpp" \ No newline at end of file diff --git a/src/bin/gtotools/gtomerge/CMakeLists.txt b/src/bin/gtotools/gtomerge/CMakeLists.txt new file mode 100644 index 000000000..d9b3b1d59 --- /dev/null +++ b/src/bin/gtotools/gtomerge/CMakeLists.txt @@ -0,0 +1,30 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "gtomerge" +) + +LIST(APPEND _sources main.cpp utf8Main.cpp) + +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE Gto +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +RV_STAGE(TYPE "EXECUTABLE" TARGET ${_target}) diff --git a/src/bin/gtotools/gtomerge/gtomerge.wrapper b/src/bin/gtotools/gtomerge/gtomerge.wrapper new file mode 100755 index 000000000..f68fb895b --- /dev/null +++ b/src/bin/gtotools/gtomerge/gtomerge.wrapper @@ -0,0 +1,50 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the APP_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set APP_HOME if invoked from a link to the script +# and APP_HOME is not already set +# + +set noglob +set name = gtomerge + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +echo RV_HOME = $RV_HOME +set platform = i386 +set bin = "$RV_HOME/bin/$name.bin.$platform" + +if (! (-e $bin) ) then + set bin = "$RV_HOME/bin/$name.bin" +endif + +if (! (-e $bin) ) then + echo "ERROR: binary " $bin " not found" + exit(-1) +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec binary + +exec $bin $*:q diff --git a/src/bin/gtotools/gtomerge/main.cpp b/src/bin/gtotools/gtomerge/main.cpp new file mode 100644 index 000000000..22f004a90 --- /dev/null +++ b/src/bin/gtotools/gtomerge/main.cpp @@ -0,0 +1,249 @@ +//****************************************************************************** +// Copyright (c) 2002 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#include "../../utf8Main.h" + +#include +#include +#include +#include +#include +#include + +using namespace Gto; +using namespace std; + + +const char *stripNamePrefix( const std::string &name, const char *prefix ) +{ + if( ! prefix ) + { + return name.c_str(); + } + if( name.substr( 0, strlen( prefix ) ) == prefix ) + { + return &(name[strlen(prefix)]); + } + return name.c_str(); +} + +void +propertyMerge(Component *out, Component *in) +{ + for (size_t i=0; i < in->properties.size(); i++) + { + Property *p = in->properties[i]; + bool found = false; + + for (size_t q=0; q < out->properties.size(); q++) + { + if (out->properties[q]->name == p->name) + { + found = true; + break; + } + } + + if (!found) + { + out->properties.push_back(p); + in->properties.erase(in->properties.begin() + i); + i--; + } + } +} + +void +componentMerge(Components& out, Components& in) +{ + for (size_t i=0; i < in.size(); i++) + { + Component *c = in[i]; + bool found = false; + + for (size_t q=0; q < out.size(); q++) + { + if (out[q]->name == c->name) + { + found = true; + propertyMerge(out[q], c); + componentMerge(out[q]->components, c->components); + break; + } + } + + if (!found) + { + out.push_back(c); + in.erase(in.begin() + i); + i--; + } + } +} + +void +objectMerge(RawDataBase *out, RawDataBase *in, const char *stripPrefix) +{ + if( stripPrefix ) + { + for (size_t i=0; i < in->objects.size(); i++) + { + Object *o = in->objects[i]; + bool found = false; + + std::string mungedNameIn( stripNamePrefix( o->name, stripPrefix ) ); + + for (size_t q=0; q < out->objects.size(); q++) + { + std::string mungedNameOut( stripNamePrefix( out->objects[q]->name, stripPrefix ) ); + if (mungedNameOut == mungedNameIn) + { + found = true; + componentMerge(out->objects[q]->components, o->components); + break; + } + } + + if (!found) + { + o->name = mungedNameIn; + out->objects.push_back(o); + in->objects.erase(in->objects.begin() + i); + i--; + } + } + } + else + { + for (size_t i=0; i < in->objects.size(); i++) + { + Object *o = in->objects[i]; + bool found = false; + + for (size_t q=0; q < out->objects.size(); q++) + { + if (out->objects[q]->name == o->name) + { + found = true; + componentMerge(out->objects[q]->components, o->components); + break; + } + } + + if (!found) + { + out->objects.push_back(o); + in->objects.erase(in->objects.begin() + i); + i--; + } + } + } +} + +void usage() +{ + cout << "gtomerge [OPTIONS] -o OUTFILE INFILE1 INFILE2 ..." << endl + << "Merge GTO property data" << endl + << endl + << "-t output as text GTO" << endl + << "-nc force uncompressed GTO" << endl + << "-sp PREFIX strip prefix" << endl + << endl; + + exit(-1); +} + +int utf8Main(int argc, char *argv[]) +{ + vector inputFiles; + char *outFile = NULL; + RawDataBase outObjects; + char *stripPrefix = NULL; + int text = 0; + int nocompress = 0; + + for (int i=1; i < argc; i++) + { + if (!strcmp(argv[i], "-o")) + { + i++; + + if (i < argc) + { + outFile = argv[i]; + } + } + else if (!strcmp(argv[i], "-sp")) + { + i++; + + if (i < argc) + { + stripPrefix = argv[i]; + } + } + else if (!strcmp(argv[i], "-t")) + { + text = 1; + } + else if (!strcmp(argv[i], "-nc")) + { + nocompress = 1; + } + else if (*argv[i] == '-') + { + usage(); + } + else + { + inputFiles.push_back(argv[i]); + } + } + + if (!inputFiles.size() || !outFile) + { + cout << "no infile or outfile specified.\n" << flush; + cout << endl; + usage(); + } + + for (size_t i=0; i < inputFiles.size(); i++) + { + RawDataBaseReader reader; + cout << "Reading input file " << inputFiles[i] << "..." << endl; + + if (!reader.open(inputFiles[i].c_str())) + { + cerr << "ERROR: unable to read file " << inputFiles[i].c_str() + << endl; + exit(-1); + } + else + { + RawDataBase *inObjects = reader.dataBase(); + objectMerge(&outObjects, inObjects, stripPrefix); + } + } + + RawDataBaseWriter writer; + Writer::FileType type = Writer::CompressedGTO; + if (nocompress) type = Writer::BinaryGTO; + if (text) type = Writer::TextGTO; + + if (!writer.write(outFile, outObjects, type)) + { + cerr << "ERROR: unable to write file " << outFile + << endl; + exit(-1); + } + else + { + cout << "Wrote file " << outFile << endl; + } + + return 0; +} diff --git a/src/bin/gtotools/gtomerge/utf8Main.cpp b/src/bin/gtotools/gtomerge/utf8Main.cpp new file mode 100644 index 000000000..69e097cc9 --- /dev/null +++ b/src/bin/gtotools/gtomerge/utf8Main.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.cpp" \ No newline at end of file diff --git a/src/bin/imgtools/CMakeLists.txt b/src/bin/imgtools/CMakeLists.txt new file mode 100644 index 000000000..68219f5f6 --- /dev/null +++ b/src/bin/imgtools/CMakeLists.txt @@ -0,0 +1,10 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +ADD_SUBDIRECTORY(rmsImageDiff) +ADD_SUBDIRECTORY(rvio) +ADD_SUBDIRECTORY(makeFBIOformats) +ADD_SUBDIRECTORY(makeMovieIOformats) +ADD_SUBDIRECTORY(rvls) diff --git a/src/bin/imgtools/makeFBIOformats/CMakeLists.txt b/src/bin/imgtools/makeFBIOformats/CMakeLists.txt new file mode 100644 index 000000000..ea14819d4 --- /dev/null +++ b/src/bin/imgtools/makeFBIOformats/CMakeLists.txt @@ -0,0 +1,45 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "makeFBIOformats" +) + +LIST(APPEND _sources main.cpp utf8Main.cpp) + +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE TwkFB Gto TwkUtil OpenEXR::OpenEXR +) + +RV_STAGE(TYPE "EXECUTABLE_WITH_PLUGINS" TARGET ${_target}) + +ADD_CUSTOM_COMMAND( + COMMENT "Generating ${RV_STAGE_PLUGINS_IMAGEFORMATS_DIR}/formats.gto" + OUTPUT ${RV_STAGE_PLUGINS_IMAGEFORMATS_DIR}/formats.gto + COMMAND $ ${RV_STAGE_PLUGINS_IMAGEFORMATS_DIR} + WORKING_DIRECTORY ${RV_STAGE_BIN_DIR} + DEPENDS ${_target} image_formats +) + +ADD_CUSTOM_TARGET( + formats.gto ALL + DEPENDS ${RV_STAGE_PLUGINS_IMAGEFORMATS_DIR}/formats.gto +) + +ADD_DEPENDENCIES(installed_image_formats formats.gto) diff --git a/src/bin/imgtools/makeFBIOformats/main.cpp b/src/bin/imgtools/makeFBIOformats/main.cpp new file mode 100644 index 000000000..dd5102784 --- /dev/null +++ b/src/bin/imgtools/makeFBIOformats/main.cpp @@ -0,0 +1,226 @@ +//****************************************************************************** +// Copyright (c) 2007 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** + +#include "../../utf8Main.h" + +#include +#include +#include +#include +#include +#include +#include + +using namespace TwkFB; +using namespace TwkUtil; +using namespace Gto; +using namespace std; + +#ifdef PLATFORM_DARWIN +#define EXT_PATTERN "dylib" +#endif + +#ifdef PLATFORM_WINDOWS +#define EXT_PATTERN "dll" +#endif + +#ifdef PLATFORM_LINUX +#define EXT_PATTERN "so" +#endif + +int utf8Main(int argc, char** argv) +{ + if (argc > 2) + { + cout << "Usage: " << argv[0] << " [directory]" << endl; + return -1; + } + + Imf::staticInitialize(); + Imf::setGlobalThreadCount(0); + TwkFB::GenericIO::init(); // Initialize TwkFB::GenericIO plugins statics + + FileNameList files; + string dir = argc == 2 ? argv[1] : "."; + + cout << "INFO: looking in " << dir << endl; + + if (filesInDirectory(pathConform(dir).c_str(), "io_*." EXT_PATTERN, files)) + { + for (int i=0; i < files.size(); i++) + { + string file = dir; + file += "/"; + file += files[i]; + file = pathConform(file); + + if (FrameBufferIO* io = GenericIO::loadPlugin(file.c_str())) + { + cout << "INFO: loaded " << file << endl; + GenericIO::addPlugin(io); + } + else + { + cerr << "WARNING: ignoring " << file << endl; + } + } + } + + Writer writer; + string outfile = dir; + if (outfile[outfile.size()-1] != '/') outfile += "/"; + outfile += "formats.gto"; + outfile = pathConform(outfile); + + if (!writer.open(outfile.c_str(), Writer::CompressedGTO)) + { + cerr << "ERROR: makeFBIOformats: can't open output file" << endl; + return -1; + } + + const GenericIO::Plugins& plugs = GenericIO::allPlugins(); + + for (GenericIO::Plugins::const_iterator i = plugs.begin(); + i != plugs.end(); + ++i) + { + + FrameBufferIO* plugin = *i; + + string fname = prefix(plugin->pluginFile()); + writer.beginObject(fname.c_str(), "imageio", 2); + + const FrameBufferIO::ImageTypeInfos& infos = + plugin->extensionsSupported(); + + writer.beginComponent(":info"); + writer.property("identifier", Gto::String, 1); + writer.property("sortkey", Gto::String, 1); + writer.endComponent(); + + for (int q=0; q < infos.size(); q++) + { + const FrameBufferIO::ImageTypeInfo& info = infos[q]; + + writer.beginComponent(info.extension.c_str()); + writer.property("description", Gto::String, 1); + writer.property("capabilities", Gto::Int, 1); + writer.property("codecs", Gto::String, + info.compressionSchemes.size(), 2); + writer.property("encodeParams", Gto::String, + info.encodeParameters.size(), 2); + writer.property("decodeParams", Gto::String, + info.decodeParameters.size(), 2); + writer.endComponent(); + + writer.intern(info.description); + writer.intern(plugin->identifier()); + writer.intern(plugin->sortKey()); + + for (int j=0; j < info.compressionSchemes.size(); j++) + { + writer.intern(info.compressionSchemes[j].first); + writer.intern(info.compressionSchemes[j].second); + } + + for (int j=0; j < info.encodeParameters.size(); j++) + { + writer.intern(info.encodeParameters[j].first); + writer.intern(info.encodeParameters[j].second); + } + + for (int j=0; j < info.decodeParameters.size(); j++) + { + writer.intern(info.decodeParameters[j].first); + writer.intern(info.decodeParameters[j].second); + } + } + + writer.endObject(); + } + + writer.beginData(); + + for (GenericIO::Plugins::const_iterator i = plugs.begin(); + i != plugs.end(); + ++i) + { + FrameBufferIO* plugin = *i; + + const FrameBufferIO::ImageTypeInfos& infos = + plugin->extensionsSupported(); + + int id = writer.lookup(plugin->identifier()); + int k = writer.lookup(plugin->sortKey()); + writer.propertyData(&id); + writer.propertyData(&k); + + for (int q=0; q < infos.size(); q++) + { + const FrameBufferIO::ImageTypeInfo& info = infos[q]; + + int s = writer.lookup(info.description); + vector schemes; + vector eparams; + vector dparams; + writer.propertyData(&s); + writer.propertyData(&info.capabilities); + + for (int j=0; j < info.compressionSchemes.size(); j++) + { + schemes.push_back(writer.lookup(info.compressionSchemes[j].first)); + schemes.push_back(writer.lookup(info.compressionSchemes[j].second)); + } + + for (int j=0; j < info.encodeParameters.size(); j++) + { + eparams.push_back(writer.lookup(info.encodeParameters[j].first)); + eparams.push_back(writer.lookup(info.encodeParameters[j].second)); + } + + for (int j=0; j < info.decodeParameters.size(); j++) + { + dparams.push_back(writer.lookup(info.decodeParameters[j].first)); + dparams.push_back(writer.lookup(info.decodeParameters[j].second)); + } + + if (schemes.empty()) + { + writer.emptyProperty(); + } + else + { + writer.propertyData(&schemes.front()); + } + + if (eparams.empty()) + { + writer.emptyProperty(); + } + else + { + writer.propertyData(&eparams.front()); + } + + if (dparams.empty()) + { + writer.emptyProperty(); + } + else + { + writer.propertyData(&dparams.front()); + } + } + } + + writer.endData(); + + TwkFB::GenericIO::shutdown(); // Shutdown TwkFB::GenericIO plugins + + return 0; +} diff --git a/src/bin/imgtools/makeFBIOformats/makeFBIOformats.wrapper b/src/bin/imgtools/makeFBIOformats/makeFBIOformats.wrapper new file mode 100755 index 000000000..39136f7c1 --- /dev/null +++ b/src/bin/imgtools/makeFBIOformats/makeFBIOformats.wrapper @@ -0,0 +1,50 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the APP_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set APP_HOME if invoked from a link to the script +# and APP_HOME is not already set +# + +set noglob +set name = makeFBIOformats + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +echo RV_HOME = $RV_HOME +set platform = i386 +set bin = "$RV_HOME/bin/$name.bin.$platform" + +if (! (-e $bin) ) then + set bin = "$RV_HOME/bin/$name.bin" +endif + +if (! (-e $bin) ) then + echo "ERROR: binary " $bin " not found" + exit(-1) +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec binary + +exec $bin $*:q diff --git a/src/bin/imgtools/makeFBIOformats/utf8Main.cpp b/src/bin/imgtools/makeFBIOformats/utf8Main.cpp new file mode 100644 index 000000000..69e097cc9 --- /dev/null +++ b/src/bin/imgtools/makeFBIOformats/utf8Main.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.cpp" \ No newline at end of file diff --git a/src/bin/imgtools/makeMovieIOformats/CMakeLists.txt b/src/bin/imgtools/makeMovieIOformats/CMakeLists.txt new file mode 100644 index 000000000..a3b35da71 --- /dev/null +++ b/src/bin/imgtools/makeMovieIOformats/CMakeLists.txt @@ -0,0 +1,63 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "makeMovieIOformats" +) + +SET(_sources + main.cpp utf8Main.cpp +) + +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE TwkMovie Gto TwkUtil OpenEXR::OpenEXR +) + +IF(RV_TARGET_LINUX) + SET(THREADS_PREFER_PTHREAD_FLAG + TRUE + ) + FIND_PACKAGE(Threads REQUIRED) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE Threads::Threads + ) +ELSEIF(RV_TARGET_WINDOWS) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE win_pthreads win_posix + ) +ENDIF() + +RV_STAGE(TYPE "EXECUTABLE_WITH_PLUGINS" TARGET ${_target}) + +ADD_CUSTOM_COMMAND( + COMMENT "Generating ${RV_STAGE_PLUGINS_MOVIEFORMATS_DIR}/movieformats.gto" + OUTPUT ${RV_STAGE_PLUGINS_MOVIEFORMATS_DIR}/movieformats.gto + COMMAND $ ${RV_STAGE_PLUGINS_MOVIEFORMATS_DIR} + WORKING_DIRECTORY ${RV_STAGE_BIN_DIR} + DEPENDS ${_target} image_formats +) + +ADD_CUSTOM_TARGET( + movieformats.gto ALL + DEPENDS ${RV_STAGE_PLUGINS_MOVIEFORMATS_DIR}/movieformats.gto +) + +ADD_DEPENDENCIES(installed_movie_formats movieformats.gto) diff --git a/src/bin/imgtools/makeMovieIOformats/main.cpp b/src/bin/imgtools/makeMovieIOformats/main.cpp new file mode 100644 index 000000000..15b0c7ab8 --- /dev/null +++ b/src/bin/imgtools/makeMovieIOformats/main.cpp @@ -0,0 +1,248 @@ +//****************************************************************************** +// Copyright (c) 2007 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#include "../../utf8Main.h" + +#ifdef _MSC_VER + // + // We are targetting at least XP, (IE no windows95, etc). + // + #define WINVER 0x0501 + #define _WIN32_WINNT 0x0501 + #include + #include + #include + #include + // + // NOTE: win_pthreads, which supplies implement.h, seems + // targetted at an earlier version of windows (pre-XP). If you + // include implement.h here, it won't compile. But as far as I + // can tell, it's not needed, so just leave it out. + // + // #include +#endif + +#include +#include +#include +#include +#include +#include +#include + +using namespace TwkMovie; +using namespace TwkUtil; +using namespace Gto; +using namespace std; + +#ifdef PLATFORM_DARWIN +#define EXT_PATTERN "dylib" +#endif + +#ifdef PLATFORM_WINDOWS +#define EXT_PATTERN "dll" +#endif + +#ifdef PLATFORM_LINUX +#define EXT_PATTERN "so" +#endif + +int utf8Main(int argc, char** argv) +{ + if (argc > 2) + { + cout << "Usage: " << argv[0] << " [directory]" << endl; + return -1; + } + + Imf::staticInitialize(); + Imf::setGlobalThreadCount(0); + + FileNameList files; + string dir = argc == 2 ? argv[1] : "."; + + cout << "INFO: looking in " << dir << endl; + + if (filesInDirectory(pathConform(dir).c_str(), "mio_*." EXT_PATTERN, files)) + { + for (int i=0; i < files.size(); i++) + { + string file = dir; + file += "/"; + file += files[i]; + file = pathConform(file); + + if (MovieIO* io = GenericIO::loadPlugin(file.c_str())) + { + cout << "INFO: loaded " << file << endl; + + GenericIO::addPlugin(io); + } + else + { + cerr << "WARNING: ignoring " << file << endl; + } + } + } + + Writer writer; + string outfile = dir; + if (outfile[outfile.size()-1] != '/') outfile += "/"; + outfile += "movieformats.gto"; + outfile = pathConform(outfile); + + if (!writer.open(outfile.c_str(), Writer::CompressedGTO)) + { + cerr << "ERROR: makeMovieIOformats: can't open output file" << endl; + return -1; + } + + const GenericIO::Plugins& plugs = GenericIO::allPlugins(); + + for (GenericIO::Plugins::const_iterator i = plugs.begin(); + i != plugs.end(); + ++i) + { + MovieIO* plugin = *i; + + string fname = prefix(plugin->pluginFile()); + writer.beginObject(fname.c_str(), "movieio", 2); + + const MovieIO::MovieTypeInfos& infos = + plugin->extensionsSupported(); + + writer.beginComponent(":info"); + writer.property("identifier", Gto::String, 1); + writer.property("sortkey", Gto::String, 1); + writer.endComponent(); + + writer.intern(plugin->identifier()); + writer.intern(plugin->sortKey()); + + for (int q=0; q < infos.size(); q++) + { + const MovieIO::MovieTypeInfo& info = infos[q]; + + writer.beginComponent(info.extension.c_str()); + writer.property("description", Gto::String, 1); + writer.property("capabilities", Gto::Int, 1); + writer.property("video_codecs", Gto::String, info.codecs.size(), 2); + writer.property("audio_codecs", Gto::String, info.audioCodecs.size(), 2); + writer.property("encode_parameters", Gto::String, info.encodeParameters.size(), 3); + writer.property("decode_parameters", Gto::String, info.decodeParameters.size(), 3); + writer.endComponent(); + + writer.intern(info.description); + + for (int j=0; j < info.codecs.size(); j++) + { + writer.intern(info.codecs[j].first); + writer.intern(info.codecs[j].second); + } + + for (int j=0; j < info.audioCodecs.size(); j++) + { + writer.intern(info.audioCodecs[j].first); + writer.intern(info.audioCodecs[j].second); + } + + for (int j=0; j < info.encodeParameters.size(); j++) + { + writer.intern(info.encodeParameters[j].name); + writer.intern(info.encodeParameters[j].description); + writer.intern(info.encodeParameters[j].codec); + } + + for (int j=0; j < info.decodeParameters.size(); j++) + { + writer.intern(info.decodeParameters[j].name); + writer.intern(info.decodeParameters[j].description); + writer.intern(info.decodeParameters[j].codec); + } + } + + writer.endObject(); + } + + writer.beginData(); + + for (GenericIO::Plugins::const_iterator i = plugs.begin(); + i != plugs.end(); + ++i) + { + MovieIO* plugin = *i; + + const MovieIO::MovieTypeInfos& infos = + plugin->extensionsSupported(); + + int id = writer.lookup(plugin->identifier()); + int k = writer.lookup(plugin->sortKey()); + writer.propertyData(&id); + writer.propertyData(&k); + + for (int q=0; q < infos.size(); q++) + { + const MovieIO::MovieTypeInfo& info = infos[q]; + + int s = writer.lookup(info.description); + vector vcodecs; + vector acodecs; + vector eparams; + vector dparams; + writer.propertyData(&s); + writer.propertyData(&info.capabilities); + + for (int j=0; j < info.codecs.size(); j++) + { + vcodecs.push_back(writer.lookup(info.codecs[j].first)); + vcodecs.push_back(writer.lookup(info.codecs[j].second)); + } + + for (int j=0; j < info.audioCodecs.size(); j++) + { + acodecs.push_back(writer.lookup(info.audioCodecs[j].first)); + acodecs.push_back(writer.lookup(info.audioCodecs[j].second)); + } + + for (int j=0; j < info.audioCodecs.size(); j++) + { + acodecs.push_back(writer.lookup(info.audioCodecs[j].first)); + acodecs.push_back(writer.lookup(info.audioCodecs[j].second)); + } + + for (int j=0; j < info.encodeParameters.size(); j++) + { + eparams.push_back(writer.lookup(info.encodeParameters[j].name)); + eparams.push_back(writer.lookup(info.encodeParameters[j].description)); + eparams.push_back(writer.lookup(info.encodeParameters[j].codec)); + } + + for (int j=0; j < info.decodeParameters.size(); j++) + { + dparams.push_back(writer.lookup(info.decodeParameters[j].name)); + dparams.push_back(writer.lookup(info.decodeParameters[j].description)); + dparams.push_back(writer.lookup(info.decodeParameters[j].codec)); + } + + if (vcodecs.empty()) writer.emptyProperty(); + else writer.propertyData(&vcodecs.front()); + + if (acodecs.empty()) writer.emptyProperty(); + else writer.propertyData(&acodecs.front()); + + if (eparams.empty()) writer.emptyProperty(); + else writer.propertyData(&eparams.front()); + + if (dparams.empty()) writer.emptyProperty(); + else writer.propertyData(&dparams.front()); + } + } + + writer.endData(); + + return 0; +} diff --git a/src/bin/imgtools/makeMovieIOformats/makeMovieIOformats.wrapper b/src/bin/imgtools/makeMovieIOformats/makeMovieIOformats.wrapper new file mode 100755 index 000000000..359185754 --- /dev/null +++ b/src/bin/imgtools/makeMovieIOformats/makeMovieIOformats.wrapper @@ -0,0 +1,50 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the APP_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set APP_HOME if invoked from a link to the script +# and APP_HOME is not already set +# + +set noglob +set name = makeMovieIOformats + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +echo RV_HOME = $RV_HOME +set platform = i386 +set bin = "$RV_HOME/bin/$name.bin.$platform" + +if (! (-e $bin) ) then + set bin = "$RV_HOME/bin/$name.bin" +endif + +if (! (-e $bin) ) then + echo "ERROR: binary " $bin " not found" + exit(-1) +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec binary + +exec $bin $*:q diff --git a/src/bin/imgtools/makeMovieIOformats/utf8Main.cpp b/src/bin/imgtools/makeMovieIOformats/utf8Main.cpp new file mode 100644 index 000000000..69e097cc9 --- /dev/null +++ b/src/bin/imgtools/makeMovieIOformats/utf8Main.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.cpp" \ No newline at end of file diff --git a/src/bin/imgtools/rmsImageDiff/CMakeLists.txt b/src/bin/imgtools/rmsImageDiff/CMakeLists.txt new file mode 100644 index 000000000..7c2459f1e --- /dev/null +++ b/src/bin/imgtools/rmsImageDiff/CMakeLists.txt @@ -0,0 +1,61 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "rmsImageDiff" +) + +LIST(APPEND _sources main.cpp utf8Main.cpp) + +IF(RV_TARGET_DARWIN) + LIST(APPEND _sources pool.mm) +ENDIF() + +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +FIND_PACKAGE( + Qt5 + COMPONENTS Core + REQUIRED +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE IOproxy + MovieProxy + MovieProcedural + MovieFB + TwkMovie + TwkExc + TwkFB + TwkMath + TwkUtil + arg +) + +IF(RV_TARGET_DARWIN) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE DarwinBundle "-framework Foundation" + ) +ELSE() + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE QTBundle + ) +ENDIF() + +RV_STAGE(TYPE "EXECUTABLE_WITH_PLUGINS" TARGET ${_target}) diff --git a/src/bin/imgtools/rmsImageDiff/main.cpp b/src/bin/imgtools/rmsImageDiff/main.cpp new file mode 100644 index 000000000..0db691db1 --- /dev/null +++ b/src/bin/imgtools/rmsImageDiff/main.cpp @@ -0,0 +1,404 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.h" + +#ifdef PLATFORM_DARWIN +#include +#else +#include +#endif +#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 + +using namespace std; +using namespace TwkUtil; +using namespace TwkFB; +using namespace TwkMath; + +FrameBuffer* convertBoth(FrameBuffer* inFB, FrameBuffer::DataType t) +{ + if (inFB->isPlanar()) + return copyConvert(inFB,t); + return copyConvertPlane(inFB,t); +} + +template +double sumChannels (const T &v) +{ + double sum = 0.0; + for (size_t n = 0 ; n < v.dimension(); ++n) + { + sum += (double) v[n]; + } + + return sum; +} + +template +int computeRMS(const FrameBuffer* a, + const FrameBuffer* b, + bool doFloat, + bool findMax, + bool doCompare, + double dmax) +{ + + Tdouble sum(0.0); + + double maxDiff = 0.0; + const double ushort_max = (double) (std::numeric_limits ::max()); + + int maxX = -1; + int maxY = -1; + + bool noMatch = false; + + if (doFloat) + { + Tfloat maxA; + Tfloat maxB; + + for (int y=0; y < a->height(); y++) + { + for (int x=0; x < a->width(); x++) + { + Tfloat ca = a->scanline(y)[x]; + Tfloat cb = b->scanline(y)[x]; + + Tdouble cd = Tdouble(ca) - Tdouble(cb); + Tdouble cd_norm = (Tdouble(ca) - Tdouble(cb)) / ushort_max; + Tdouble sq = cd_norm * cd_norm; + sum += sq; + if (findMax) + { + double sumSq = sumChannels(sum); + if (sumSq > maxDiff) + { + maxDiff = sumSq; + maxX = x; + maxY = y; + maxA = ca; + maxB = cb; + } + } else if (doCompare) { + for (int c=0; c < a->numChannels(); ++c) + { + if (fabs((double) cd[c]) > dmax) + { + cout << "Pixel difference at (" << x << ", " << y << ") channel[" << c + << "] = " << fabs((double) cd[c]) << " found." << endl; + noMatch = true; + break; + } + } + } + if (noMatch) break; + } + if (noMatch) break; + } + + if (doCompare) + { + if (noMatch) + { + cout << "Images are NOT matched." << endl; + return 1; + } + else + { + cout << "Images are matched." << endl; + return 0; + } + } + + sum /= Tdouble(a->height() * a->width()); + + for (size_t n = 0 ; n < sum.dimension(); ++n) + { + cout << "" << sqrt(sum[n]); + } + cout << endl; + + if (findMax && maxX > -1) + { + cout << "max diff at (" << maxX << ", " << maxY << "): " << + maxA << " vs. " << maxB << endl; + } + } + else + { + Tunsigned maxA; + Tunsigned maxB; + + for (int y=0; y < a->height(); y++) + { + for (int x=0; x < a->width(); x++) + { + Tunsigned ca = a->scanline(y)[x]; + Tunsigned cb = b->scanline(y)[x]; + + Tdouble cd = Tdouble(ca) - Tdouble(cb); + Tdouble cd_norm = (Tdouble(ca) - Tdouble(cb)) / ushort_max; + Tdouble sq = cd_norm * cd_norm; + sum += sq; + if (findMax) + { + double sumSq = sumChannels(sum); + if (sumSq > maxDiff) + { + maxDiff = sumSq; + maxX = x; + maxY = y; + maxA = ca; + maxB = cb; + } + } else if (doCompare) { + for (int c=0; c < a->numChannels(); ++c) + { + if (fabs((double) cd[c]) > dmax) + { + cout << "Pixel difference at (" << x << ", " << y << ") channel[" << c + << "] = " << fabs((double) cd[c]) << " found." << endl; + noMatch = true; + break; + } + } + } + if (noMatch) break; + } + if (noMatch) break; + } + + if (doCompare) + { + if (noMatch) + { + cout << "Images are NOT matched." << endl; + return 1; + } + else + { + cout << "Images are matched." << endl; + return 0; + } + } + + sum /= Tdouble(a->height() * a->width()); + for (size_t n = 0 ; n < sum.dimension(); ++n) + { + cout << "" << sqrt(sum[n]); + } + cout << endl; + + if (findMax && maxX > -1) + { + cout << "max diff at (" << maxX << ", " << maxY << "): " << + maxA << " vs. " << maxB << endl; + } + } + + return 0; +} + +int utf8Main(int argc, char *argv[]) +{ +#ifdef PLATFORM_DARWIN + TwkApp::DarwinBundle bundle("RV", MAJOR_VERSION, MINOR_VERSION, REVISION_NUMBER); +#else + QCoreApplication qapp(argc, argv); + TwkApp::QTBundle bundle("rv", MAJOR_VERSION, MINOR_VERSION, REVISION_NUMBER); + (void) bundle.top(); +#endif + + char* input1 = 0; + char* input2 = 0; + + bool doFloat = false; + bool findMax = false; + bool doCompare = false; + double dmax = 0.0; + + // + // Parse cmd line args + // + + string usage = string("usage: ") + argv[0] + " [-f] [-m] [-cmp] [-dmax ] \n" + + " -f: operate on FLOAT pixels\n" + + " -m: print location and values of pixel with max difference\n" + + " -cmp: perform image comparison (returns exit 1 if not matched)" + + " -dmax: Maximum allow channel error during comparison (default=0.0)"; + + for (int i = 1; i < argc; ++i) + { + if (strcmp(argv[i], "-f") == 0) doFloat = true; + else if (strcmp(argv[i], "-m") == 0) findMax = true; + else if (strcmp(argv[i], "-cmp") == 0) doCompare = true; + else if (strcmp(argv[i], "-dmax") == 0 && (i+1 < argc)) dmax = atof(argv[++i]); + else if (!input1) input1 = argv[i]; + else if (!input2) input2 = argv[i]; + } + if (argc < 3 || argc > 8 || !input1 || !input2) + { + cerr << usage; + exit(-1); + } + + string in1 = pathConform(input1); + string in2 = pathConform(input2); + + TwkFB::GenericIO::init(); // Initialize TwkFB::GenericIO plugins statics + TwkMovie::GenericIO::init(); // Initialize TwkMovie::GenericIO plugins statics + + try + { + TwkFB::loadProxyPlugins("TWK_FB_PLUGIN_PATH"); + TwkMovie::loadProxyPlugins("TWK_MOVIE_PLUGIN_PATH"); + } + catch (...) + { + cerr << "WARNING: a problem occured while loading plugins." << endl; + cerr << " some plugins may not have been loaded." << endl; + } + + TwkMovie::GenericIO::addPlugin(new TwkMovie::MovieFBIO()); + TwkMovie::GenericIO::addPlugin(new TwkMovie::MovieProceduralIO()); + + // + // Add the statically linked in types + // + + GenericIO::FrameBufferVector aImages, bImages; + FrameBufferIO::ReadRequest request; + + try + { + cout << "INFO: reading " << in1 << endl; + GenericIO::readImages(aImages, in1, request); + cout << "INFO: reading " << in2 << endl; + GenericIO::readImages(bImages, in2, request); + } + catch (exception& exc) + { + cerr << "ERROR: while reading images: " << exc.what() << endl; + exit(-1); + } + + FrameBuffer* ai = aImages.front(); + FrameBuffer* bi = bImages.front(); + +// cout << "a -> "; ai->outputInfo(cout); cout << " planar: " << ai->isPlanar() << endl; +// cout << "b -> "; bi->outputInfo(cout); cout << " planar: " << bi->isPlanar() << endl; + if (ai->isPlanar()) + { + FrameBuffer* temp = mergePlanes(ai); + ai = temp; + } + if (bi->isPlanar()) + { + FrameBuffer* temp = mergePlanes(bi); + bi = temp; + } + + FrameBuffer* a = convertBoth(ai, (doFloat) ? FrameBuffer::FLOAT : FrameBuffer::USHORT); + FrameBuffer* b = convertBoth(bi, (doFloat) ? FrameBuffer::FLOAT : FrameBuffer::USHORT); + + if (a->width() != b->width() || a->height() != b->height()) + { + cerr << "ERROR: incompatible images" << endl; + cerr << " sizes do not match" << endl; + exit(-1); + } + + + if (a->numChannels() != b->numChannels()) + { + cerr << "ERROR: incompatible images" << endl; + cerr << " channel size does not match" << endl; + exit(-1); + } + + if (a->numChannels() > 4 || b->numChannels() > 4) + { + cerr << "ERROR: only works on images with 1-4 channels." << endl; + exit(-1); + } + + if (a->orientation() != b->orientation()) + { + flip(b); + } + +// cout << "a -> "; a->outputInfo(cout); cout << " planar: " << a->isPlanar() << endl; +// cout << "b -> "; b->outputInfo(cout); cout << " planar: " << b->isPlanar() << endl; + + +// if (a->dataType() != b->dataType()) +// { +// FrameBuffer::DataType t; + +// if (a->dataType() < b->dataType()) +// { +// t = a->dataType(); +// } +// else +// { +// t = b->dataType(); +// } + +// if (a->dataType() != t) a = convertBoth(a, t); +// if (b->dataType() != t) b = convertBoth(b, t); +// } +// + + int status = 0; + + + if (a->numChannels() == 1) + { + status = computeRMS(a, b, doFloat, findMax, doCompare, dmax); + } + else if (a->numChannels() == 2) + { + status = computeRMS(a, b, doFloat, findMax, doCompare, dmax); + } + else if (a->numChannels() == 3) + { + status = computeRMS(a, b, doFloat, findMax, doCompare, dmax); + } + else if (a->numChannels() == 4) + { + status = computeRMS(a, b, doFloat, findMax, doCompare, dmax); + } + + TwkMovie::GenericIO::shutdown(); // Shutdown TwkMovie::GenericIO plugins + TwkFB::GenericIO::shutdown(); // Shutdown TwkFB::GenericIO plugins + + return 0; +} diff --git a/src/bin/imgtools/rmsImageDiff/pool.h b/src/bin/imgtools/rmsImageDiff/pool.h new file mode 100644 index 000000000..a32190a0d --- /dev/null +++ b/src/bin/imgtools/rmsImageDiff/pool.h @@ -0,0 +1,14 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifndef __rv__pool__h__ +#define __rv__pool__h__ + +void initPool(); +void releasePool(); + +#endif // __rv__pool__h__ diff --git a/src/bin/imgtools/rmsImageDiff/pool.mm b/src/bin/imgtools/rmsImageDiff/pool.mm new file mode 100644 index 000000000..2eb2e9547 --- /dev/null +++ b/src/bin/imgtools/rmsImageDiff/pool.mm @@ -0,0 +1,22 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** + +#include "pool.h" +#import + +static NSAutoreleasePool* pool = 0; + +void initPool() +{ + pool = [[NSAutoreleasePool alloc] init]; +} + +void releasePool() +{ + [pool release]; +} diff --git a/src/bin/imgtools/rmsImageDiff/rmsImageDiff.wrapper b/src/bin/imgtools/rmsImageDiff/rmsImageDiff.wrapper new file mode 100755 index 000000000..19e91c932 --- /dev/null +++ b/src/bin/imgtools/rmsImageDiff/rmsImageDiff.wrapper @@ -0,0 +1,50 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the APP_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set APP_HOME if invoked from a link to the script +# and APP_HOME is not already set +# + +set noglob +set name = rmsImageDiff + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +echo RV_HOME = $RV_HOME +set platform = i386 +set bin = "$RV_HOME/bin/$name.bin.$platform" + +if (! (-e $bin) ) then + set bin = "$RV_HOME/bin/$name.bin" +endif + +if (! (-e $bin) ) then + echo "ERROR: binary " $bin " not found" + exit(-1) +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec binary + +exec $bin $*:q diff --git a/src/bin/imgtools/rmsImageDiff/utf8Main.cpp b/src/bin/imgtools/rmsImageDiff/utf8Main.cpp new file mode 100644 index 000000000..69e097cc9 --- /dev/null +++ b/src/bin/imgtools/rmsImageDiff/utf8Main.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.cpp" \ No newline at end of file diff --git a/src/bin/imgtools/rvio/CMakeLists.txt b/src/bin/imgtools/rvio/CMakeLists.txt new file mode 100644 index 000000000..e48194d18 --- /dev/null +++ b/src/bin/imgtools/rvio/CMakeLists.txt @@ -0,0 +1,85 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "rvio" +) + +LIST(APPEND _sources UICommands.cpp main.cpp utf8Main.cpp) + +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +FIND_PACKAGE( + Qt5 + COMPONENTS Core Gui Widgets + REQUIRED +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE Mu + MuLang + MuTwkApp + IOproxy + MovieProxy + OpenEXR::OpenEXR + PyTwkApp + RvApp + IPCore + IPBaseNodes + MovieRV_FBO + MovieFB + MovieMuDraw + MovieProcedural + TwkAudio + TwkMovie + TwkCMS + TwkMath + TwkDeploy + TwkExc + TwkFB + TwkUtil + TwkQtBase + arg + stl_ext + TwkMediaLibrary + Qt5::Core + TwkGLFFBO +) + +IF(RV_TARGET_LINUX) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE Qt5::Gui Qt5::Widgets GLEW::GLEW BDWGC::Gc QTBundle + ) +ELSEIF(RV_TARGET_WINDOWS) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE Qt5::Gui Qt5::Widgets glew gc QTBundle + ) +ELSEIF(RV_TARGET_DARWIN) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE BDWGC::Gc DarwinBundle + ) +ENDIF() + +TARGET_COMPILE_OPTIONS( + ${_target} + PRIVATE "-DRVIO_HW=1" "-DGIT_HEAD=\"${RV_GIT_COMMIT_SHORT_HASH}\"" "-DRELEASE_DESCRIPTION=\"${RV_RELEASE_DESCRIPTION}\"" +) + +RV_STAGE(TYPE "EXECUTABLE_WITH_PLUGINS" TARGET ${_target}) diff --git a/src/bin/imgtools/rvio/UICommands.cpp b/src/bin/imgtools/rvio/UICommands.cpp new file mode 100644 index 000000000..22a26bdf8 --- /dev/null +++ b/src/bin/imgtools/rvio/UICommands.cpp @@ -0,0 +1,512 @@ +//****************************************************************************** +// Copyright (c) 2001-2005 Tweak Inc. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** + +#include +#include + +namespace RVIO { + +using namespace TwkApp; +using namespace Mu; +using namespace std; + +void initUICommands(MuLangContext* context) +{ + USING_MU_FUNCTION_SYMBOLS; + MuLangContext* c = context; + Symbol* root = context->globalScope(); + Name cname = c->internName("commands"); + Mu::Module* commands = root->findSymbolOfType(cname); + + commands->addSymbols(new Function(c, "resizeFit", resizeFit, None, + Return, "void", + End), + + new Function(c, "setViewSize", setViewSize, None, + Return, "void", + Parameters, + new Param(c, "width", "int"), + new Param(c, "height", "int"), + End), + + new Function(c, "popupMenu", popupMenu, None, + Return, "void", + Parameters, + new Param(c, "event", "Event"), + new Param(c, "menu", "MenuItem[]", Value(Pointer(0))), + End), + + new Function(c, "popupMenuAtPoint", popupMenuAtPoint, None, + Return, "void", + Parameters, + new Param(c, "x", "int"), + new Param(c, "y", "int"), + new Param(c, "menu", "MenuItem[]", Value(Pointer(0))), + End), + + new Function(c, "setWindowTitle", setWindowTitle, None, + Return, "void", + Parameters, + new Param(c, "title", "string"), + End), + + new Function(c, "center", center, None, + Return, "void", + End), + + new Function(c, "close", close, None, + Return, "void", + End), + + new Function(c, "toggleMenuBar", toggleMenuBar, None, + Return, "void", + End), + + new Function(c, "isMenuBarVisible", isMenuBarVisible, None, + Return, "bool", + End), + + new SymbolicConstant(c, "OneExistingFile", "int", Value(0)), + new SymbolicConstant(c, "ManyExistingFiles", "int", Value(0)), + new SymbolicConstant(c, "ManyExistingFilesAndDirectories", "int", Value(0)), + new SymbolicConstant(c, "OneFileName", "int", Value(0)), + new SymbolicConstant(c, "OneDirectory", "int", Value(0)), + + new Function(c, "openMediaFileDialog", openMediaFileDialog, + None, + Return, "string[]", + Parameters, + new Param(c, "associated", "bool"), + new Param(c, "selectType", "int"), + new Param(c, "filter", "string", Value(0)), + new Param(c, "defaultPath", "string", Value(0)), + new Param(c, "label", "string", Value(0)), + End), + + new Function(c, "openFileDialog", openFileDialog, + None, + Return, "string[]", + Parameters, + new Param(c, "associated", "bool"), + new Param(c, "multiple", "bool", Value(false)), + new Param(c, "directory", "bool", Value(false)), + new Param(c, "filter", "string", Value(0)), + new Param(c, "defaultPath", "string", Value(0)), + End), + + new Function(c, "saveFileDialog", saveFileDialog, + None, + Return, "string", + Parameters, + new Param(c, "associated", "bool"), + new Param(c, "filter", "string", Value(0)), + new Param(c, "defaultPath", "string", Value(0)), + End), + + new SymbolicConstant(c, "CursorNone", "int", Value(0)), + new SymbolicConstant(c, "CursorArrow", "int", Value(2)), + new SymbolicConstant(c, "CursorDefault", "int", Value(1)), + + new Function(c, "setCursor", setCursor, None, + Return, "void", + Parameters, + new Param(c, "cursorType", "int"), + End), + + new SymbolicConstant(c, "InfoAlert", "int", Value(0)), + new SymbolicConstant(c, "WarningAlert", "int", Value(1)), + new SymbolicConstant(c, "ErrorAlert", "int", Value(2)), + + new Function(c, "stereoSupported", stereoSupported, None, + Return, "bool", + End), + + new Function(c, "alertPanel", alertPanel, None, + Return, "int", + Parameters, + new Param(c, "associated", "bool"), + new Param(c, "type", "int"), + new Param(c, "title", "string"), + new Param(c, "message", "string"), + new Param(c, "button0", "string"), + new Param(c, "button1", "string"), + new Param(c, "button2", "string"), + End), + + new Function(c, "watchFile", watchFile, None, + Return, "void", + Parameters, + new Param(c, "filename", "string"), + new Param(c, "watch", "bool"), + End), + + new Function(c, "showConsole", showConsole, None, + Return, "void", + End), + + new Function(c, "isConsoleVisible", isConsoleVisible, None, + Return, "bool", + End), + // network + + new Function(c, "remoteSendMessage", remoteSendMessage, None, + Return, "void", + Parameters, + new Param(c, "message", "string"), + new Param(c, "recipients", "string[]", Value(0)), + End), + + new Function(c, "remoteSendEvent", remoteSendEvent, None, + Return, "void", + Parameters, + new Param(c, "event", "string"), + new Param(c, "target", "string"), + new Param(c, "contents", "string"), + new Param(c, "recipients", "string[]", Value(0)), + End), + + new Function(c, "remoteConnections", remoteConnections, None, + Return, "string[]", + End), + + new Function(c, "remoteApplications", remoteApplications, None, + Return, "string[]", + End), + + new Function(c, "remoteContacts", remoteContacts, None, + Return, "string[]", + End), + + new Function(c, "remoteLocalContactName", remoteLocalContactName, None, + Return, "string", + End), + + new Function(c, "remoteConnect", remoteConnect, None, + Return, "void", + Parameters, + new Param(c, "name", "string"), + new Param(c, "host", "string"), + new Param(c, "port", "int", Value(0)), + End), + + new Function(c, "remoteDisconnect", remoteDisconnect, None, + Return, "void", + Parameters, + new Param(c, "remoteContact", "string"), + End), + + new Function(c, "remoteNetwork", remoteNetwork, None, + Return, "void", + Parameters, + new Param(c, "on", "bool"), + End), + + new SymbolicConstant(c, "NetworkStatusOn", "int", Value(1)), + new SymbolicConstant(c, "NetworkStatusOff", "int", Value(0)), + + new Function(c, "remoteNetworkStatus", remoteNetworkStatus, None, + Return, "int", + End), + + new Function(c, "writeSetting", writeSetting, None, + Return, "void", + Parameters, + new Param(c, "group", "string"), + new Param(c, "name", "string"), + new Param(c, "value", "SettingsValue"), + End), + + new Function(c, "readSetting", readSetting, None, + Return, "SettingsValue", + Parameters, + new Param(c, "group", "string"), + new Param(c, "name", "string"), + new Param(c, "defaultValue", "SettingsValue"), + End), + + new Function(c, "httpGet", httpGet, None, + Return, "void", + Parameters, + new Param(c, "url", "string"), + new Param(c, "headers", "[(string,string)]"), + new Param(c, "replyEvent", "string"), + new Param(c, "authenticationEvent", "string", Value(Pointer(0))), + new Param(c, "progressEvent", "string", Value(Pointer(0))), + new Param(c, "ignoreSslErrors", "bool", Value(false)), + End), + + new Function(c, "httpPost", httpPost, None, + Return, "void", + Parameters, + new Param(c, "url", "string"), + new Param(c, "headers", "[(string,string)]"), + new Param(c, "postString", "string"), + new Param(c, "replyEvent", "string"), + new Param(c, "authenticationEvent", "string", Value(Pointer(0))), + new Param(c, "progressEvent", "string", Value(Pointer(0))), + new Param(c, "ignoreSslErrors", "bool", Value(false)), + End), + + new Function(c, "mainWindowWidget", mainWindowWidget, None, + Return, "qt.QMainWindow", + End), + + new Function(c, "networkAccessManager", networkAccessManager, None, + Return, "qt.QNetworkAccessManager", + End), + + new Function(c, "javascriptMuExport", javascriptMuExport, None, + Return, "void", + Parameters, + new Param(c, "frame", "qt.QWebFrame"), + End), + + new Function(c, "sessionFromUrl", sessionFromUrl, None, + Return, "void", + Parameters, + new Param(c, "url", "string"), + End), + + new Function(c, "putUrlOnClipboard", putUrlOnClipboard, None, + Return, "void", + Parameters, + new Param(c, "url", "string"), + new Param(c, "title", "string"), + new Param(c, "doEncode", "bool", Value(true)), + End), + + new Function(c, "myNetworkPort", myNetworkPort, None, + Return, "int", + End), + + new Function(c, "myNetworkHost", myNetworkHost, None, + Return, "string", + End), + + new Function(c, "encodePassword", encodePassword, + None, + Return, "string", + Parameters, + new Param(c, "password", "string"), + End), + + new Function(c, "decodePassword", decodePassword, + None, + Return, "string", + Parameters, + new Param(c, "password", "string"), + End), + + new Function(c, "cacheDir", cacheDir, + None, + Return, "string", + End), + + new Function(c, "openUrl", openUrl, + None, + Return, "void", + Parameters, + new Param(c, "url", "string"), + End), + + EndArguments); +} + +NODE_IMPLEMENTATION(resizeFit, void) +{ +} + +NODE_IMPLEMENTATION(setViewSize, void) +{ +} + +NODE_IMPLEMENTATION(popupMenu, void) +{ +} + +NODE_IMPLEMENTATION(popupMenuAtPoint, void) +{ +} + +NODE_IMPLEMENTATION(setWindowTitle, void) +{ +} + +NODE_IMPLEMENTATION(center, void) +{ +} + +NODE_IMPLEMENTATION(close, void) +{ +} + +NODE_IMPLEMENTATION(toggleMenuBar, void) +{ +} + +NODE_IMPLEMENTATION(isMenuBarVisible, bool) +{ + NODE_RETURN(false); +} + +NODE_IMPLEMENTATION(openMediaFileDialog, Pointer) +{ + NODE_RETURN((Pointer)0); +} + +NODE_IMPLEMENTATION(openFileDialog, Pointer) +{ + NODE_RETURN((Pointer)0); +} + +NODE_IMPLEMENTATION(saveFileDialog, Pointer) +{ + NODE_RETURN((Pointer)0); +} + +NODE_IMPLEMENTATION(setCursor, void) +{ +} + +NODE_IMPLEMENTATION(alertPanel, int) +{ + NODE_RETURN(0); +} + +NODE_IMPLEMENTATION(stereoSupported, bool) +{ + NODE_RETURN(false); +} + +NODE_IMPLEMENTATION(watchFile, void) +{ +} + +NODE_IMPLEMENTATION(showConsole, void) +{ +} + +NODE_IMPLEMENTATION(isConsoleVisible, bool) +{ + NODE_RETURN(false); +} + + +NODE_IMPLEMENTATION(remoteSendMessage, void) +{ +} + +NODE_IMPLEMENTATION(remoteSendEvent, void) +{ +} + +NODE_IMPLEMENTATION(remoteConnections, Pointer) +{ + NODE_RETURN(0); +} + +NODE_IMPLEMENTATION(remoteApplications, Pointer) +{ + NODE_RETURN(0); +} + +NODE_IMPLEMENTATION(remoteContacts, Pointer) +{ + NODE_RETURN(0); +} + +NODE_IMPLEMENTATION(remoteLocalContactName, Pointer) +{ + NODE_RETURN(0); +} + +NODE_IMPLEMENTATION(remoteConnect, void) +{ +} + +NODE_IMPLEMENTATION(remoteDisconnect, void) +{ +} + +NODE_IMPLEMENTATION(remoteNetwork, void) +{ +} + +NODE_IMPLEMENTATION(remoteNetworkStatus, int) +{ + NODE_RETURN(0); +} + +NODE_IMPLEMENTATION(writeSetting, void) +{ +} + +NODE_IMPLEMENTATION(readSetting, Pointer) +{ + NODE_RETURN(0); +} + +NODE_DECLARATION(httpGet, void) +{ +} + +NODE_DECLARATION(httpPost, void) +{ +} + +NODE_DECLARATION(sessionFromUrl, void) +{ +} + +NODE_DECLARATION(putUrlOnClipboard, void) +{ +} + +NODE_DECLARATION(myNetworkPort, int) +{ + NODE_RETURN(0); +} + +NODE_DECLARATION(encodePassword, Mu::Pointer) +{ + NODE_RETURN(0); +} + +NODE_DECLARATION(decodePassword, Mu::Pointer) +{ + NODE_RETURN(0); +} + +NODE_DECLARATION(cacheDir, Mu::Pointer) +{ + NODE_RETURN(0); +} + +NODE_IMPLEMENTATION(openUrl, void) +{ +} + +NODE_DECLARATION(mainWindowWidget, Mu::Pointer) +{ + NODE_RETURN(0); +} + +NODE_DECLARATION(networkAccessManager, Mu::Pointer) +{ + NODE_RETURN(0); +} + +NODE_DECLARATION(javascriptMuExport, void) +{ +} + +NODE_IMPLEMENTATION(myNetworkHost, Pointer) +{ + NODE_RETURN(0); +} + +} // End namespace RVIO diff --git a/src/bin/imgtools/rvio/UICommands.h b/src/bin/imgtools/rvio/UICommands.h new file mode 100644 index 000000000..9c232186a --- /dev/null +++ b/src/bin/imgtools/rvio/UICommands.h @@ -0,0 +1,65 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifndef __RVIO__UICommands__h__ +#define __RVIO__UICommands__h__ + +#include +#include +#include + +namespace RVIO { + +void initUICommands(Mu::MuLangContext*); + +NODE_DECLARATION(resizeFit, void); +NODE_DECLARATION(setViewSize, void); +NODE_DECLARATION(popupMenu, void); +NODE_DECLARATION(popupMenuAtPoint, void); +NODE_DECLARATION(setWindowTitle, void); +NODE_DECLARATION(center, void); +NODE_DECLARATION(close, void); +NODE_DECLARATION(toggleMenuBar, void); +NODE_DECLARATION(isMenuBarVisible, bool); +NODE_DECLARATION(openMediaFileDialog, Mu::Pointer); +NODE_DECLARATION(openFileDialog, Mu::Pointer); +NODE_DECLARATION(saveFileDialog, Mu::Pointer); +NODE_DECLARATION(setCursor, void); +NODE_DECLARATION(alertPanel, int); +NODE_DECLARATION(stereoSupported, bool); +NODE_DECLARATION(watchFile, void); +NODE_DECLARATION(showConsole, void); +NODE_DECLARATION(isConsoleVisible, bool); +NODE_DECLARATION(remoteSendMessage, void); +NODE_DECLARATION(remoteSendEvent, void); +NODE_DECLARATION(remoteApplications, Mu::Pointer); +NODE_DECLARATION(remoteConnections, Mu::Pointer); +NODE_DECLARATION(remoteContacts, Mu::Pointer); +NODE_DECLARATION(remoteLocalContactName, Mu::Pointer); +NODE_DECLARATION(remoteConnect, void); +NODE_DECLARATION(remoteDisconnect, void); +NODE_DECLARATION(remoteNetwork, void); +NODE_DECLARATION(remoteNetworkStatus, int); +NODE_DECLARATION(writeSetting, void); +NODE_DECLARATION(readSetting, Mu::Pointer); +NODE_DECLARATION(httpGet, void); +NODE_DECLARATION(httpPost, void); +NODE_DECLARATION(sessionFromUrl, void); +NODE_DECLARATION(putUrlOnClipboard, void); +NODE_DECLARATION(myNetworkPort, int); +NODE_DECLARATION(encodePassword, Mu::Pointer); +NODE_DECLARATION(decodePassword, Mu::Pointer); +NODE_DECLARATION(cacheDir, Mu::Pointer); +NODE_DECLARATION(mainWindowWidget, Mu::Pointer); +NODE_DECLARATION(networkAccessManager, Mu::Pointer); +NODE_DECLARATION(javascriptMuExport, void); +NODE_DECLARATION(openUrl, void); +NODE_DECLARATION(myNetworkHost, Mu::Pointer); + +} // RVIO + +#endif // __RV__UICommands__h__ diff --git a/src/bin/imgtools/rvio/main.cpp b/src/bin/imgtools/rvio/main.cpp new file mode 100644 index 000000000..df431a682 --- /dev/null +++ b/src/bin/imgtools/rvio/main.cpp @@ -0,0 +1,1684 @@ +//****************************************************************************** +// Copyright (c) 2001-2005 Tweak Inc. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#include "../../utf8Main.h" + +#if defined(_MSC_VER) && _MSC_VER < 1900 +#include +#include +#include +#endif + +#ifdef PLATFORM_DARWIN +#include +#else +#include +#endif + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifdef PLATFORM_WINDOWS +#include +#include +#include +#endif + +#include + +#include + +#ifndef PLATFORM_WINDOWS +#include +#include +#include +#endif + +#ifdef PLATFORM_WINDOWS +#undef uint16_t +#endif + +#ifdef PLATFORM_DARWIN +#include +#endif + +#include + + +typedef TwkContainer::StringProperty StringProperty; + +extern void TwkMovie_GenericIO_setDebug(bool); +extern void TwkFB_GenericIO_setDebug(bool); + +using namespace std; +using namespace TwkFB; +using namespace TwkMovie; +using namespace TwkUtil; +using namespace TwkMath; +using namespace TwkAudio; + +struct Illuminant +{ + const char* name; + float x; + float y; +}; + +Illuminant standardIlluminants[] = { + {"A", 0.44757f, 0.40745f }, + {"B", 0.34842f, 0.35161f}, + {"C", 0.31006f, 0.31616f}, + {"D50", 0.34567f, 0.35850f}, + {"D55", 0.33242f, 0.34743f}, + {"D65", 0.31271f, 0.32902f}, + {"D65REC709", 0.3127f, 0.3290f}, + {"D75", 0.29902f, 0.31485f}, + {"E", 1.0f/3.0f, 1.0f/3.0f}, + {"F1", 0.31310f, 0.33727f}, + {"F2", 0.37208f, 0.37529f}, + {"F3", 0.40910f, 0.39430f}, + {"F4", 0.44018f, 0.40329f}, + {"F5", 0.31379f, 0.34531f}, + {"F6", 0.37790f, 0.38835f}, + {"F7", 0.31292f, 0.32933f}, + {"F8", 0.34588f, 0.35875f}, + {"F9", 0.37417f, 0.37281f}, + {"F10", 0.34609f, 0.35986f}, + {"F11", 0.38052f, 0.37713f}, + {"F12", 0.43695f, 0.40441f}, + {NULL, 0, 0} +}; + +int parseInFiles(int argc, char *argv[]) +{ + // cerr << "Application::parseInFiles " << argc << " files" << endl; + Rv::Options& opts = Rv::Options::sharedOptions(); + + for (int i=0; iname; i++) + { + if (!strcmp(i->name, name)) + { + x = i->x; + y = i->y; + return true; + } + } + + return false; +} + +char* filename = 0; +int showFormats = 0; +char* outputFile = 0; +char* licarg = 0; +int lqt_decoder = 0; +int showVersion = 0; +float filegamma = 1.0; +float ingamma = 1.0; +float outgamma = 1.0; +float exposure = 0.0; +float scale = 1.0; +int iomethod = -1; +int iosize = 61440; +int resizex = 0; +int resizey = 0; +int loglin = 0; +int loglinc = 0; +int redloglin = 0; +int redlogfilmlin = 0; +int linlog = 0; +int verbose = 0; +int reallyverbose = 0; +int outbits = 0; +int processFloat = 0; +char* outtype = 0; +int ysamples = 0; +int rysamples = 0; +int bysamples = 0; +int usamples = 0; +int vsamples = 0; +int asamples = 0; +int xsize = 0; +int ysize = 0; +int flipImage = 0; +int flopImage = 0; +int strictlicense = 0; +char* compressor = (char*)""; +char* codec = (char*)""; +char* audioCodec = (char*)""; +float quality = 0.9f; +char* paRatio = (char*)"1:1"; +float pixelAspect = 1.0; +int paNumerator = 1; +int paDenominator = 1; +char* timerange = 0; +int stretch = 0; +char* comment = (char*)""; +char* copyright = (char*)""; +char* initscript = 0; +int err2out = 0; +int inpremult = 0; +int inunpremult = 0; +int outpremult = 0; +int outunpremult = 0; +float audioRate = 0; +int audioChannels = 0; +char* outStereo = (char*)""; +float outfps = 0; +int nosession = 0; +int outhalf = 0; +int out8 = 0; +int leaderFrames = 1; +int outrgb = 0; +char* dlut = 0; +int insrgb = 0; +int in709 = 0; +int outsrgb = 0; +int out709 = 0; +int outaces = 0; +int outlogc = 0; +int outlogcEI = 0; +int outredlog = 0; +int outredlogfilm = 0; +float white0 = -999; +float white1 = -999; +char* illumName = (char*)""; +int outadaptive = 0; +int tio = 0; +int threads = 1; +int wthreads = -1; +int noprerender = 0; +char* resampleMethod = (char*)"area"; +char* view = 0; + +static void control_c_handler(int sig) +{ + cout << "INFO: stopped by user" << endl; + exit(1); +} + + +vector inputFiles; +vector inchmap; +vector outchmap; +vector > leaderArgs; +vector > tailArgs; +vector > overlayArgs; +vector writerArgs; +float timerwait = 1.0 / 192; +deque outparams; + + +#ifndef WIN32 +extern "C" { +int GC_pthread_create(pthread_t *new_thread, + const pthread_attr_t *attr, + void *(*start_routine)(void *), void *arg); +#ifdef PLATFORM_DARWIN +int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset); +#endif +int GC_pthread_join(pthread_t thread, void **retval); +int GC_pthread_detach(pthread_t thread); +} +#endif + +void +parseParam (string s) +{ + string::size_type pos = s.find("="); + + if (pos != string::npos) + { + outparams.push_back(TwkUtil::StringPair(s.substr(0, pos), + s.substr(pos+1, string::npos))); + } +} + +int +parseOutParams(int argc, char *argv[]) +{ + for (int i=0; i args; + for (int i=0; i args; + for (int i=0; iinfo().audioSampleRate; + } + + str << ".movieproc"; + + // + // In HW rvio we create a MovieRV_FBO instead of just a MovieProcedural + // + + MovieRV* mp = new MovieRV(); + Rv::RvSession* session = new Rv::RvSession(); + session->setBatchMode(true); + session->read(str.str().c_str(), IPCore::Session::ReadRequest()); + session->setViewNode(session->sources()[0]->group()->name()); + + int xs = omov->info().width; + int ys = omov->info().height; + + TwkMovie::MovieInfo info; + + info.width = xs; + info.height = ys; + info.uncropWidth = xs; + info.uncropHeight = ys; + info.numChannels = 4; + info.audioSampleRate = omov->hasAudio() ? omov->info().audioSampleRate : 0; + info.audioChannels = omov->info().audioChannels; + + mp->open(session, info, info.audioChannels, audioRate); + lmov = mp; + + for (size_t q = 0; q < leaderArgs.size(); q++) + { + MovieMuDraw* m = new MovieMuDraw(lmov, context, process); + m->setFunction(leaderArgs[q].front(), leaderArgs[q]); + lmov = m; + } + } + + return lmov; +} + +TwkMovie::Movie* +makeOverlayMovie(TwkMovie::Movie* in, + Mu::MuLangContext* context, + Mu::Process* process) +{ + if (!overlayArgs.empty()) + { + for (size_t q = 0; q < overlayArgs.size(); q++) + { + MovieMuDraw* m = new MovieMuDraw(in, context, process); + m->setFunction(overlayArgs[q].front(), overlayArgs[q]); + in = m; + } + } + + return in; +} + +MovieRV* +makeInputMovie() +{ + Rv::Options& opts = Rv::Options::sharedOptions(); + + opts.readerThreads = threads; + + int xs = xsize; + int ys = ysize; + + MovieRV* rvmov = new MovieRV(); + Rv::RvSession* session = new Rv::RvSession(); + session->setBatchMode(true); + + if (inputFiles.size() == 1 && + extension(inputFiles.front()) == "rv") + { + session->read(inputFiles.front().c_str(), IPCore::Session::ReadRequest()); + } + else + { + session->readUnorganizedFileList(inputFiles); + // + // If only one input source, and it's not a session file, + // output the source directly instead of the default + // seq, so that output frame numbers match, unless the + // user specified a view. + // + if (session->sources().size() == 1 && !view) + { + session->setViewNode(session->sources()[0]->group()->name()); + cerr << "INFO: setting view to single Source" << endl; + } + } + + if (*outStereo && strcmp(outStereo, "separate")) + { + vector props; + session->findPropertyOfType(props, "#RVDisplayStereo.stereo.type"); + props.front()->front() = outStereo; + } + + if (resizey || resizex) + { + Rv::resizeAllInputs(session->graph(), resizex, resizey); + } + + TwkMath::Vec2i size = session->maxSize(); + + if (xs == 0 || ys == 0) + { + xs = resizex; + ys = resizey; + + float aspect = float(size[0]) / float(size[1]); + if (!ys) ys = int(xs / aspect + 0.49f); + else if (!xs) xs = int(ys * aspect + 0.49f); + } + + if (xs == 0 || ys == 0) + { + xs = size[0]; + ys = size[1]; + } + + xs = int(float(xs) * scale + 0.49f); + ys = int(float(ys) * scale + 0.49f); + + TwkMovie::MovieInfo info; + info.width = xs; + info.height = ys; + info.uncropWidth = xs; + info.uncropHeight = ys; + info.numChannels = 4; + info.audioSampleRate = audioRate; + info.audioChannels = layoutChannels(channelLayouts(audioChannels).front()); + + + if (view && !session->setViewNode(view)) + { + cout << "ERROR: view not found \"" << view << "\"" << endl; + exit(-1); + } + // + // We show the user no cache stats, so don't bother to compute them. + // + session->graph().cache().setCacheStatsDisabled(true); + + session->setSessionStateFromNode(session->graph().viewNode()); + rvmov->open(session, info, info.audioChannels, audioRate); + + if (loglin) Rv::setLogLinOnAll(session->graph(), true, 1); + if (loglinc) Rv::setLogLinOnAll(session->graph(), true, 3); + if (redloglin) Rv::setLogLinOnAll(session->graph(), true, 6); + if (redlogfilmlin) Rv::setLogLinOnAll(session->graph(), true, 7); + if (insrgb) Rv::setSRGBLinOnAll(session->graph(), true); + if (in709) Rv::setRec709LinOnAll(session->graph(), true); + + if (ingamma != 1.0) Rv::setGammaOnAll(session->graph(), ingamma); + if (filegamma != 1.0) Rv::setFileGammaOnAll(session->graph(), filegamma); + + if (exposure != 0.0) + { + Rv::setFileExposureOnAll(session->graph(), exposure); + exposure = 0; + } + + if (flipImage || flopImage) + { + Rv::setFlipFlopOnAll(session->graph(), flipImage, true, flopImage, true); + flipImage = 0; + flopImage = 0; + } + + if (!inchmap.empty()) Rv::setChannelMapOnAll(session->graph(), inchmap); + Rv::fitAllInputs(session->graph(), xs, ys); + + if (dlut) + { + try + { + session->readLUT(dlut, "#RVDisplayColor", true); + } + catch (std::exception& exc) + { + cerr << "ERROR: " << exc.what() << endl; + exit(-1); + } + } + + if (opts.fileLUT && strcmp(opts.fileLUT, "")) + { + try + { + session->readLUTOnAll(opts.fileLUT, "RVLinearize", true); + } + catch (std::exception& exc) + { + cerr << "ERROR: " << exc.what() << endl; + exit(-1); + } + } + + if (opts.lookLUT && strcmp(opts.lookLUT, "")) + { + try + { + session->readLUTOnAll(opts.lookLUT, "RVLookLUT", true); + } + catch (std::exception& exc) + { + cerr << "ERROR: " << exc.what() << endl; + exit(-1); + } + } + + return rvmov; +} + +TwkMovie::Movie* +makeFormattedMovie(MovieWriter::WriteRequest& writeRequest, + TwkMovie::Movie* omov, + TwkMovie::Movie* lmov) +{ + // + // Reformatter for the content + // + + ReformattingMovie* rlmov = lmov ? new ReformattingMovie(lmov) : 0; + ReformattingMovie* rmov = new ReformattingMovie(omov); + + if (*illumName) + { + if (!findillum(illumName, white0, white1)) + { + cerr << "ERROR: " << illumName + << " is not a standard illuminant" + << endl; + exit(-1); + } + } + + rmov->setOutputGamma(outgamma); + rmov->setUseFloatingPoint(processFloat); + rmov->setVerbose(reallyverbose); + rmov->setOutputLogSpace(linlog); + rmov->setOutputSRGB(outsrgb); + rmov->setOutputRec709(out709); + rmov->setOutput709toACES(outaces); + rmov->setOutputLogC(outlogc); + rmov->setOutputLogCEI(outlogcEI); + rmov->setOutputRedLog(outredlog); + rmov->setOutputRedLogFilm(outredlogfilm); + if (white0 != -999 && white1 != -999) rmov->setOutputWhite(white0, white1); + rmov->setFlip(flipImage); + rmov->setFlop(flopImage); + if (outpremult) rmov->setOutputPremultiply(); + if (outunpremult) rmov->setOutputUnpremultiply(); + if (!outchmap.empty()) rmov->setChannelMap(outchmap); + + if (rlmov) + { + rlmov->setVerbose(reallyverbose); + rlmov->setOutputLogSpace(linlog); + rlmov->setOutputRedLog(outredlog); + rlmov->setOutputRedLogFilm(outredlogfilm); + if (!outchmap.empty()) rlmov->setChannelMap(outchmap); + rlmov->setFlip(flipImage); + rlmov->setFlop(flopImage); + if (outpremult) rlmov->setOutputPremultiply(); + if (outunpremult) rlmov->setOutputUnpremultiply(); + } + + if (ysamples) + { + if (rysamples) + { + rmov->convertToYRYBY(ysamples, rysamples, bysamples, asamples); + + if (rlmov) + { + rlmov->convertToYRYBY(ysamples, rysamples, bysamples, asamples); + } + } + else if (usamples) + { + rmov->convertToYUV(ysamples, usamples, vsamples); + + if (rlmov) + { + rlmov->convertToYUV(ysamples, usamples, vsamples); + } + } + } + + if (outtype && outbits) + { + bool fp = !strcmp(outtype, "float") || + !strcmp(outtype, "half") || + !strcmp(outtype, "double"); + + if (outbits <= 8) + { + rmov->setOutputFormat(FrameBuffer::UCHAR); + if (rlmov) rlmov->setOutputFormat(FrameBuffer::UCHAR); + } + else if (outbits <= 16) + { + rmov->setOutputFormat(fp ? FrameBuffer::HALF : FrameBuffer::USHORT); + if (rlmov) rlmov->setOutputFormat(fp ? FrameBuffer::HALF : FrameBuffer::USHORT); + } + else + { + rmov->setOutputFormat(fp ? FrameBuffer::FLOAT : FrameBuffer::USHORT); + if (rlmov) rlmov->setOutputFormat(fp ? FrameBuffer::FLOAT : FrameBuffer::USHORT); + } + } + + omov = rmov; + lmov = rlmov; + + // + // Sequence leader and content if leader exists + // + + if (lmov) + { + int len = lmov->info().end - lmov->info().start + 1; + int fs = lmov->info().start; + int f0 = writeRequest.frames.empty() ? omov->info().start : writeRequest.frames.front(); + int f1 = writeRequest.frames.empty() ? omov->info().end : writeRequest.frames.back(); + LeaderFooterMovie* smov = new LeaderFooterMovie(omov, f0, f1, + lmov, + 0); + omov = smov; + + FrameList lframes; + for (int f = lmov->info().start; f <= lmov->info().end; f++) + { + lframes.push_back(f); + } + + if (!writeRequest.frames.empty()) + { + writeRequest.frames.insert(writeRequest.frames.begin(), + lframes.begin(), + lframes.end()); + } + } + + return omov; +} + +TwkMovie::Movie* +makeMovieTree(MovieWriter::WriteRequest& writeRequest, + Mu::MuLangContext* context, + Mu::Process* process) +{ + MovieRV* reader = makeInputMovie(); + + // + // Unless they were specified up front by a -t directive, + // makeMovieTree must collect some frames. If tio is true then we need to + // get those frames trimmed to the session in/out otherwise we can use the + // full range of the reader. + // + + if (writeRequest.frames.empty()) + { + int fs = reader->info().start; + int fe = reader->info().end; + int inc = reader->info().inc; + if (tio) + { + fs = reader->session()->inPoint(); + fe = reader->session()->outPoint() - 1; + inc = reader->session()->inc(); + } + writeRequest.timeRangeOverride = true; + + // + // Enforce forward incraments and fill the frames list smallest to + // largest. + // + + for (int i=min(fs,fe); i <= max(fs,fe); i+=abs(inc)) + { + writeRequest.frames.push_back(i); + } + } + + // + // Add any overlays, etc + // + + TwkMovie::Movie* omov = makeOverlayMovie(reader, context, process); + TwkMovie::Movie* lmov = makeLeaderMovie(writeRequest, omov, context, process); + TwkMovie::Movie* outmov = makeFormattedMovie(writeRequest, omov, lmov); + //TwkMovie::Movie* outmov = makeFormattedMovie(writeRequest, reader, 0); + + return outmov; +} + +string +str(float f) +{ + ostringstream str; + str << f; + return str.str(); +} + +string +str(int i) +{ + ostringstream str; + str << i; + return str.str(); +} + +void +addParam(const string& name, const string& val) +{ + outparams.push_front(StringPair(name, val)); +} + +void +addDefaultParams() +{ + if (outfps != 0.0) addParam("output/fps", str(outfps)); + + if (outgamma != 1.0) + { + addParam("output/gamma", str(outgamma)); + addParam("output/transfer", TwkFB::ColorSpace::Gamma()); + } + + if (outsrgb) addParam("output/transfer", TwkFB::ColorSpace::sRGB()); + if (out709) addParam("output/transfer", TwkFB::ColorSpace::Rec709()); + if (outlogc) + { + if (outlogcEI == 0) addParam("output/transfer", TwkFB::ColorSpace::ArriLogC()); + else addParam("output/transfer", TwkFB::ColorSpace::ArriLogC() + " EI=" + str(outlogcEI)); + } + + if (outaces) + { + addParam("output/ACES", ""); + addParam("output/transfer", TwkFB::ColorSpace::Linear()); + addParam("output/primaries", TwkFB::ColorSpace::ACES()); + // NOTE: these go rX,rY,gX,gY,bX,bY,wX,wY + addParam("output/chromaticities", "0.73470,0.26530,0.0,1.0,0.00010,-0.07700,0.32168,0.33767"); + addParam("output/neutral", "0.32168,0.33767"); + } + + if (linlog) + { + // should add black/white codes too someday + addParam("output/transfer", TwkFB::ColorSpace::CineonLog()); + } + + if (outredlog) + { + addParam("output/transfer", TwkFB::ColorSpace::RedLog()); + } + + if (outredlogfilm) + { + addParam("output/transfer", TwkFB::ColorSpace::RedLogFilm()); + } + + if (outpremult) addParam("output/alpha", "PREMULT"); + else if (outunpremult) addParam("output/alpha", "UNPREMULT"); + + if (paNumerator) + { + addParam("output/pa/numerator", str(paNumerator)); + addParam("output/pa/denominator", str(paDenominator)); + } + else + { + addParam("output/pa", paRatio); + } +} + +bool +parsePA() +{ + string pa = paRatio; + + if (pa != "1:1") + { + // + // first check for correct syntax of pa + // + + int colons = 0; + int dots = 0; + + for (size_t i = 0; i < pa.size(); i++) + { + char c = pa[i]; + if (c != ':' && c != '.' && (c < '0' || c > '9')) return false; + if (c == '.') dots++; + if (c == ':') colons++; + } + + if (dots != 0 && colons != 0) return false; + + // + // pull it apart if needed + // + + if (colons) + { + string::size_type i = pa.find(':'); + paNumerator = atoi(pa.substr(0, i).c_str()); + paDenominator = atoi(pa.substr(i+1, pa.size() - i).c_str()); + } + else + { + pixelAspect = atof(pa.c_str()); + paNumerator = 0; + paDenominator = 0; + } + } + + return true; +} + +void +writeSession(string outfile) +{ + if (!(overlayArgs.empty() && leaderArgs.empty())) + { + cerr << "WARNING: \"-overlay\" and \"-leader\" " << + "ignored when outputting session" << endl; + } + + MovieRV* reader = makeInputMovie(); + + TwkApp::Document::WriteRequest request; + request.setOption("tag", string("")); + request.setOption("compressed", false); + request.setOption("writeAsCopy", false); + request.setOption("partial", false); + request.setOption("sparse", false); + + cout << "INFO: writing session: " << outfile << endl; + + reader->session()->write(outfile.c_str(), request); +} + +void setEnvVar(const string& var, const string& val) +{ +#ifdef WIN32 + ostringstream str; + str << var << "=" << val; + putenv(str.str().c_str()); +#else + setenv(var.c_str(), val.c_str(), 1); +#endif +} + +//---------------------------------------------------------------------- + +int +utf8Main(int argc, char *argv[]) +{ + setEnvVar("LANG", "C"); + setEnvVar("LC_ALL", "C"); + TwkFB::ThreadPool::initialize(); + + // + // XXX dummyDev is leaking here. Best would be to pass it to the App so + // that it could delete it after startup, since it's no longer needed at + // that point. + // + + TwkGLF::FBOVideoDevice* dummyDev = new TwkGLF::FBOVideoDevice(0, 10, 10, false); + IPCore::ImageRenderer::queryGL(); + const char* glVersion =(const char*)glGetString(GL_SHADING_LANGUAGE_VERSION); + IPCore::Shader::Function::useShadingLanguageVersion(glVersion); + + #ifndef PLATFORM_WINDOWS + // + // Check the per-process limit on open file descriptors and + // reset the soft limit to the hard limit. + // + struct rlimit rlim; + getrlimit (RLIMIT_NOFILE, &rlim); + rlim.rlim_cur = rlim.rlim_max; + setrlimit (RLIMIT_NOFILE, &rlim); + #endif + +#ifdef PTW32_STATIC_LIB + pthread_win32_process_attach_np(); + pthread_win32_process_attach_np(); +#endif + + +#ifdef PLATFORM_DARWIN + TwkApp::DarwinBundle bundle("RV", MAJOR_VERSION, MINOR_VERSION, REVISION_NUMBER); +#endif + +#ifdef PLATFORM_LINUX + QCoreApplication qapp(argc, argv); + TwkApp::QTBundle bundle("rv", MAJOR_VERSION, MINOR_VERSION, REVISION_NUMBER); +#endif + +#ifdef PLATFORM_WINDOWS + QApplication qapp(argc, argv); + TwkApp::QTBundle bundle("rv", MAJOR_VERSION, MINOR_VERSION, REVISION_NUMBER); +#endif + + Rv::Options& opts = Rv::Options::sharedOptions(); + + opts.exrcpus = SystemInfo::numCPUs(); + Rv::Options::manglePerSourceArgs(argv, argc); + IPCore::ImageRenderer::defaultAllowPBOs(false); + Imf::staticInitialize(); + IPCore::Application::cacheEnvVars(); + + IPCore::AudioRenderer::setNoAudio(true); + +#ifdef PLATFORM_WINDOWS + glewInit(NULL); +#endif + + // + // Call the deploy functions + // + + TWK_DEPLOY_APP_OBJECT dobj(MAJOR_VERSION, + MINOR_VERSION, + REVISION_NUMBER, + argc, argv, + RELEASE_DESCRIPTION, + "HEAD=" GIT_HEAD); + + // + // Set some opts defaults for rvio that differ from RV + // + + int sleepTime = 0; + + opts.exrInherit = 0; + opts.exrNoOneChannel = 0; + opts.exrReadWindowIsDisplayWindow = 0; + opts.exrReadWindow = 1; // DisplayWindow + opts.dpxPixel = (char*)"A2_BGR10"; + opts.cinPixel = (char*)"A2_BGR10"; + +#ifdef PLATFORM_WINDOWS + opts.exrIOMethod = 2; + opts.dpxIOMethod = 2; + opts.cinIOMethod = 2; + opts.tgaIOMethod = 2; + opts.jpegIOMethod = 2; + opts.tiffIOMethod = 2; +#else + opts.exrIOMethod = 2; + opts.dpxIOMethod = 2; + opts.cinIOMethod = 2; + opts.tgaIOMethod = 2; + opts.jpegIOMethod = 2; + opts.tiffIOMethod = 2; +#endif + + // + // We handle the '--help' flag by changing it to '-help' for + // argparse to work. + // + for (int i = 0; i < argc; ++i) + { + if (!strcmp("--help", argv[i])) + { + strcpy(argv[i], "-help"); + break; + } + } + + // + // Parse cmd line args + // + + if (arg_parse + (argc, argv, + "", "\nUsage: RVIO (hardware version) movie and image sequence conversion and creation", + "", "", + "", " Make Movie: rvio in.#.tif -o out.mov", + "", " Convert Image: rvio in.tif -o out.jpg", + "", " Convert Image Seq.: rvio in.#.tif -o out.#.jpg", + "", " Movie With Audio: rvio [ in.#.tif in.wav ] -o out.mov", + "", " Movie With LUT: rvio [ -llut log2film.csp in.#.dpx ] -o out.mov", + "", " Rip Movie Range #1: rvio in.mov -t 1000-1200 -o out.mov", + "", " Rip Movie Range #2: rvio in.mov -t 1000-1200 -o out.#.jpg", + "", " Rip Movie Audio: rvio in.mov -o out.wav", + "", " Conform Image: rvio in.tif -outres 512 512 -o out.tif", + "", " Resize Image: rvio in.#.tif -scale 0.25 -o out.#.jpg", + "", " Resize/Stretch: rvio in.#.tif -resize 640 480 -o out.#.jpg", + "", " Resize Keep Aspect: rvio in.#.tif -resize 1920 0 -o out.#.jpg", + "", " Resize Keep Aspt #2: rvio in.#.tif -resize 0 1080 -o out.#.jpg", + "", " Sequence: rvio cut1.#.tif cut2.mov cut3.1-100#.dpx -o out.mov", + "", " Per-Source Arg: rvio [ -pa 2.0 -fps 30 cut1.#.dpx ] cut2.mov -o out.mov", + "", " Stereo Movie File: rvio [ left.mov right.mov ] -outstereo separate -o out.mov", + "", " Stereo Anaglyph: rvio [ left.mov right.mov ] -outstereo anaglyph -o out.mov", + "", " Log Cin/DPX to Movie: rvio -inlog -outsrgb in.#.cin -o out.mov", + "", " Output Log Cin/DPX: rvio -outlog in.#.exr -o out.#.dpx", + "", " OpenEXR 16 Bit Out: rvio in.#.dpx -outhalf -o out.#.exr", + "", " OpenEXR to 8 Bit: rvio in.#.exr -out8 -o out.#.tif", + "", " OpenEXR B44 4:2:0: rvio in.#.exr -outhalf -yryby 1 2 2 -codec B44 -o out.#.exr", + "", " OpenEXR B44A 4:2:0: rvio in.#.exr -outhalf -yrybya 1 2 2 1 -codec B44A -o out.#.exr", + "", " OpenEXR DWAA 4:2:0: rvio in.#.exr -outhalf -yryby 1 2 2 -quality 45 -codec DWAA -o out.#.exr", + "", " OpenEXR DWAB 4:2:0: rvio in.#.exr -outhalf -yrybya 1 2 2 1 -quality 45 -codec DWAB -o out.#.exr", + "", " ACES from PD DPX: rvio in.#.dpx -inlog -outhalf -outaces out.#.aces", + "", " ACES from JPEG: rvio in.#.jpg -insrgb -outhalf -outaces out.#.aces", + "", " Chng White to D75: rvio in.#.exr -outillum D75 -outhalf -o out.#.exr", + "", " Chng White to D75 #2: rvio in.#.exr -outwhite 0.29902 0.31485 -outhalf -o out.#.exr", + "", " TIFF 32 Bit Float: rvio in.#.tif -outformat 32 float -o out.#.tif", + "", " Anamorphic Unsqueeze: rvio [ -pa 2.0 in_2k_full_ap.#.dpx ] -outres 2048 1556/2 -o out_2k.mov", + "", " Camera JPEG to EXR: rvio -insrgb IMG1234.jpg -o out.exr", + "", " Letterbox HD in 1.33: rvio [ -uncrop 1920 1444 0 182 in1080.#.dpx ] -outres 640 480 -o out.mov", + "", " Crop 2.35 of Full Ap: rvio [ -crop 0 342 2047 1213 inFullAp.#.dpx ] -o out.mov", + "", " Multiple CPUs: rvio -v -rthreads 3 in.#.dpx -o out.mov", + "", " Test Throughput: rvio -v in.#.dpx -o out.null", + "", "", + "", "Advanced EXR/ACES Header Attributes Usage:", + "", " Multiple -outparam values can be used.", + "", " Type names: f, i, s, sv -- float, int, string, string vector [N values]", + "", " v2i, v2f, v3i, v3f -- 2D and 3D int and float vectors [2 or 3 values required]", + "", " b2i, b2f -- 2D box float and int [4 values required]", + "", " c -- chromaticities [8 values required]", + "", " Passthrough syntax: -outparams passthrough=REGEX", + "", " Attr creation syntax: -outparams NAME:TYPE=VALUE0[,VALUE1,...]\"", + "", " EXIF attrs: rvio exif.jpg -insrgb -o out.exr -outparams \"passthrough=.*EXIF.*\"", + "", " Create float attr: rvio in.exr -o out.exr -outparams pi:f=3.14", + "", " Create v2i attr: rvio in.exr -o out.exr -outparams myV2iAttr:v2i=1,2", + "", " Create string attr: rvio in.exr -o out.exr -outparams \"myAttr:s=HELLO WORLD\"", + "", " Chromaticies (XYZ): rvio XYZ.tiff -o out.exr -outparams chromaticities:c=1,0,0,1,0,0,.333333,.3333333", + "", " No Color Adaptation: rvio in.exr -o out.aces -outaces -outillum D65REC709", + "", "", + "", "Example Leader/Overlay Usage:", + "", " simpleslate: side-text Field1=Value1 Field2=Value2 ...", + "", " watermark: text opacity", + "", " frameburn: opacity grey font-point-size", + "", " bug: file.tif opacity height", + "", " matte: aspect-ratio opacity", + "", "", + "", " Movie w/Slate: rvio in.#.jpg -o out.mov -leader simpleslate \"FilmCo\" \\", + "", " \"Artist=Jane Q. Artiste\" \"Shot=S01\" \"Show=BlockBuster\" \\", + "", " \"Comments=You said it was too blue so I made it red\"", + "", " Movie w/Watermark: rvio in.#.jpg -o out.mov -overlay watermark \"FilmCo Eyes Only\" .25", + "", " Movie w/Frame Burn: rvio in.#.jpg -o out.mov -overlay frameburn .4 1.0 30.0", + "", " Movie w/Bug: rvio in.#.jpg -o out.mov -overlay bug logo.tif 0.4 128 15 100", + "", " Movie w/Matte: rvio in.#.jpg -o out.mov -overlay matte 2.35 0.8", + "", " Multiple: rvio ... -leader ... -overlay ... -overlay ...", + "", "", + RV_ARG_SEQUENCE_HELP, + "", "", + RV_ARG_SOURCE_OPTIONS(opts), + "", "", + "", "Global arguments", + "", "", + "", ARG_SUBR(parseInFiles), "Input sequence patterns, images, movies, or directories ", + "-o %S", &outputFile, "Output sequence or image", + "-t %S", &timerange, "Output time range (default=input time range)", + "-tio", ARG_FLAG(&tio), "Output time range from view's in/out points", + "-v", ARG_FLAG(&verbose), "Verbose messages", + "-vv", ARG_FLAG(&reallyverbose), "Really Verbose messages", + "-q", ARG_FLAG(&processFloat), "Best quality color conversions (not necessary, slower)", + "-ns", ARG_FLAG(&opts.nukeSequence), "Nuke-style sequences (deprecated and ignored -- no longer needed)", + "-noRanges", ARG_FLAG(&opts.noRanges), "No separate frame ranges (i.e. 1-10 will be considered a file)", \ + "-rthreads %d", &threads, "Number of reader/render threads (default=1)", + "-wthreads %d", &wthreads, "Number of writer threads (limited support for this)", + "-view %S", &view, "View to render (default=defaultSequence or current view in RV file)", + "-noSequence", ARG_FLAG(&opts.noSequence), "Don't contract files into sequences", \ + "-formats", ARG_FLAG(&showFormats), "Show all supported image and movie formats", + "-leader", ARG_SUBR(parseLeader), "Insert leader/slate (can use multiple time)", + "-leaderframes %d", &leaderFrames, "Number of leader frames (default=1)", + "-overlay", ARG_SUBR(parseOverlay), "Visual overlay(s) (can use multiple times)", + "-inlog", ARG_FLAG(&loglin), "Convert input to linear space via Cineon Log->Lin", + "-inredlog", ARG_FLAG(&redloglin), "Convert input to linear space via Red Log->Lin", + "-inredlogfilm", ARG_FLAG(&redlogfilmlin), "Convert input to linear space via Red Log Film->Lin", + "-insrgb", ARG_FLAG(&insrgb), "Convert input to linear space from sRGB space", + "-in709", ARG_FLAG(&in709), "Convert input to linear space from Rec-709 space", + "-ingamma %f", &ingamma, "Convert input using gamma correction", + "-filegamma %f", &filegamma, "Convert input using gamma correction to linear space", + "-inchannelmap", ARG_SUBR(parseInChannels), "map input channels", + "-inpremult", ARG_FLAG(&inpremult), "premultiply alpha and color", + "-inunpremult", ARG_FLAG(&inunpremult), "un-premultiply alpha and color", + "-exposure %f", &exposure, "Apply relative exposure change (in stops)", + "-scale %f", &scale, "Scale input image geometry", + "-resize %d [%d]", &resizex, &resizey, "Resize input image geometry to exact size on input", +// "-resampleMethod %S", &resampleMethod, "Resampling method (area, linear, cubic, nearest, default=%s)", resampleMethod, + "-dlut %S", &dlut, "Apply display LUT", + "-flip", ARG_FLAG(&flipImage), "Flip image (flip vertical) (keep orientation flags the same)", + "-flop", ARG_FLAG(&flopImage), "Flop image (flip horizontal) (keep orientation flags the same)", + "-yryby %d %d %d", &ysamples, &rysamples, &bysamples, "Y RY BY sub-sampled planar output", + "-yrybya %d %d %d %d", &ysamples, &rysamples, &bysamples, &asamples, "Y RY BY A sub-sampled planar output", + "-yuv %d %d %d", &ysamples, &usamples, &vsamples, "Y U V sub-sampled planar output", + "-outparams", ARG_SUBR(&parseOutParams), "Codec specific output parameters", \ + "-outchannelmap", ARG_SUBR(parseOutChannels), "map output channels", + "-outrgb", ARG_FLAG(&outrgb), "same as -outchannelmap R G B", + "-outpremult", ARG_FLAG(&outpremult), "premultiply alpha and color", + "-outunpremult", ARG_FLAG(&outunpremult), "un-premultiply alpha and color", + "-outlog", ARG_FLAG(&linlog), "Convert output to log space via Cineon Lin->Log", + "-outsrgb", ARG_FLAG(&outsrgb), "Convert output to sRGB ColorSpace", + "-out709", ARG_FLAG(&out709), "Convert output to Rec-709 ColorSpace", + "-outredlog", ARG_FLAG(&outredlog), "Convert output to Red Log ColorSpace", + "-outredlogfilm", ARG_FLAG(&outredlogfilm), "Convert output to Red Log Film ColorSpace", + "-outgamma %f", &outgamma, "Apply gamma to output", + "-outstereo", ARG_SUBR(&parseOutStereo), "Output stereo (checker, scanline, anaglyph, left, right, pair, mirror, hsqueezed, vsqueezed, default=separate)", + "-outformat %d %S", &outbits, &outtype, "Output bits and format (e.g. 16 float -or- 8 int)", + "-outhalf", ARG_FLAG(&outhalf), "Same as -outformat 16 float", + "-out8", ARG_FLAG(&out8), "Same as -outformat 8 int", + "-outres %d %d", &xsize, &ysize, "Output resolution", + "-outfps %f", &outfps, "Output FPS", + "-outaces", ARG_FLAG(&outaces), "Output ACES gamut (converts pixels to ACES)", + "-outwhite %f %f", &white0, &white1, "Output white CIE 1931 chromaticity x, y", + "-outillum %S", &illumName, "Output standard illuminant name (A-C, D50, D55, D65, D65REC709, D75 E, F[1-12])", + "-codec %S", &codec, "Output codec (varies with file format)", + "-audiocodec %S", &audioCodec, "Output audio codec (varies with file format)", + "-audiorate %f", &audioRate, "Output audio sample rate (default 48000)", + "-audiochannels %d", &audioChannels, "Output audio channels (default 2)", + "-quality %f", &quality, "Output codec quality 0.0 -> 1.0 (100000 for DWAA/DWAB) (varies w/format and codec default=%g)", quality, + "-outpa %S", &paRatio, "Output pixel aspect ratio (e.g. 1.33 or 16:9, etc. metadata only) default=%s", paRatio, + "-comment %S", &comment, "Ouput comment (movie files, default=\"%s\")", comment, + "-copyright %S", ©right, "Ouput copyright (movie files, default=\"%s\")", copyright, + "-lic %S", &licarg, "Use specific license file (this param is simply ignored for Open RV as it is not required)", + "-debug", ARG_SUBR(&Rv::parseDebugKeyWords), "Debug category", \ + "-version", ARG_FLAG(&showVersion), "Show RVIO version number", + "-iomethod %d [%d]", &iomethod, &iosize, "I/O Method (overrides all) (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=%d) and optional chunk size (default=%d)", iomethod, iosize, \ + "-exrcpus %d", &opts.exrcpus, "EXR thread count (default=%d)", opts.exrcpus, \ + "-exrRGBA", ARG_FLAG(&opts.exrRGBA), "EXR Always read as RGBA (default=false)", \ + "-exrInherit", ARG_FLAG(&opts.exrInherit), "EXR guess channel inheritance (default=false)", \ + "-exrNoOneChannel", ARG_FLAG(&opts.exrNoOneChannel), "EXR never use one channel planar images (default=false)", \ + "-exrIOMethod %d [%d]", &opts.exrIOMethod, &opts.exrIOSize, "EXR I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=%d) and optional chunk size (default=%d)", opts.exrIOMethod, opts.exrIOSize, \ + "-exrReadWindowIsDisplayWindow", ARG_FLAG(&opts.exrReadWindowIsDisplayWindow), "EXR read window is display window (default=false)", \ + "-exrReadWindow %d", &opts.exrReadWindow, "EXR Read Window Method (0=Data, 1=Display, 2=Union, 3=Data inside Display, default=%d)", opts.exrReadWindow, \ + "-jpegRGBA", ARG_FLAG(&opts.jpegRGBA), "Make JPEG four channel RGBA on read (default=no, use RGB or YUV)", \ + "-jpegIOMethod %d [%d]", &opts.jpegIOMethod, &opts.jpegIOSize, "JPEG I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=%d) and optional chunk size (default=%d)", opts.exrIOMethod, opts.exrIOSize, \ + "-cinpixel %S", &opts.cinPixel, "Cineon pixel storage (default=%s)", opts.cinPixel, \ + "-cinchroma", ARG_FLAG(&opts.cinchroma), "Use Cineon chromaticity values (for default reader only)", \ + "-cinIOMethod %d [%d]", &opts.cinIOMethod, &opts.cinIOSize, "Cineon I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=%d) and optional chunk size (default=%d)", opts.cinIOMethod, opts.cinIOSize, \ + "-dpxpixel %S", &opts.dpxPixel, "DPX pixel storage (default=%s)", opts.dpxPixel, \ + "-dpxchroma", ARG_FLAG(&opts.dpxchroma), "Use DPX chromaticity values (for default reader only)", \ + "-dpxIOMethod %d [%d]", &opts.dpxIOMethod, &opts.dpxIOSize, "DPX I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=%d) and optional chunk size (default=%d)", opts.dpxIOMethod, opts.dpxIOSize, \ + "-tgaIOMethod %d [%d]", &opts.tgaIOMethod, &opts.tgaIOSize, "TARGA I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=%d) and optional chunk size (default=%d)", opts.tgaIOMethod, opts.tgaIOSize, \ + "-tiffIOMethod %d [%d]", &opts.tiffIOMethod, &opts.tiffIOSize, "TIFF I/O Method (0=standard, 1=buffered, 2=unbuffered, 3=MemoryMap, 4=AsyncBuffered, 5=AsyncUnbuffered, default=%d) and optional chunk size (default=%d)", opts.tgaIOMethod, opts.tgaIOSize, \ + "-init %S", &initscript, "Override init script", + "-err-to-out", ARG_FLAG(&err2out), "Output errors to standard output (instead of standard error)", + "-strictlicense", ARG_FLAG(&strictlicense), "Exit rather than consume an RV license if no rvio licenses are available (this param is simply ignored for Open RV as it is not required)", + //"-noprerender", ARG_FLAG(&noprerender), "Turn off prerendering optimization", + "-flags", ARG_SUBR(&Rv::parseMuFlags), "Arbitrary flags (flag, or 'name=value') for Mu", +#if defined(PLATFORM_WINDOWS) && !defined(NDEBUG) + "-sleep %d", &sleepTime, "Sleep (in seconds) before starting to allow attaching debugger", +#endif + NULL) < 0) + { + exit(-1); + } + + TwkMovie::MovieWriter::setReallyVerbose(reallyverbose); + + // + // If the usere hasn't specified a number of writer threads, use the number + // of reader threads. + // + + if (wthreads == -1) wthreads = threads; + + opts.delaySessionLoading = 0; + + // Note that some additional work would be required to enable progressive + // source loading in RVIO. Some synchronization is required to know when + // all the sources have been actually loaded. + // With the aim of minimizing risks for the RV 7.8.0, this specific + // optimization task has been intentionally postponed. + opts.progressiveSourceLoading = 0; + + // + // Set up ENV vars for IO plugins based on opts + // + + if (iomethod != -1) + { + // + // iomethod overrides all specific iomethods for backwards compat + // + + opts.exrIOMethod = iomethod; + opts.dpxIOMethod = iomethod; + opts.cinIOMethod = iomethod; + opts.tgaIOMethod = iomethod; + opts.jpegIOMethod = iomethod; + opts.tiffIOMethod = iomethod; + opts.exrIOSize = iosize; + opts.dpxIOSize = iosize; + opts.cinIOSize = iosize; + opts.tgaIOSize = iosize; + opts.jpegIOSize = iosize; + opts.tiffIOSize = iosize; + } + + opts.exportIOEnvVars(); + + // + // Sleep is used for debugging on windows -- need time to manually attach + // + +#if defined(PLATFORM_WINDOWS) && !defined(NDEBUG) + if (sleepTime > 0) + { + cout << "INFO: sleeping " << sleepTime << " seconds" << endl; + Sleep(sleepTime * 1000); + cout << "INFO: continuing after sleep" << endl; + } +#endif + + if (!parsePA()) + { + cout << "ERROR: bad pixel aspect ratio syntax" << endl + << " you can use a scalar float: e.g. 1.33" << endl + << " or a ratio of integers: e.g. 16:9" << endl; + + return -1; + } + + // + // Add default params first so that later user params will + // override them. E.g. if you do -out709 and then for dpx + // transfer=USER it will put USER in the header instead of + // REC709. + // + + addDefaultParams(); + + if (const char *envParams = getenv("RVIO_OUTPARAMS")) + { + string s(envParams); + vector params; + stl_ext::tokenize(params, s, " "); + for (int i = 0; i < params.size(); ++i) parseParam(params[i]); + } + + if (!opts.initializeAfterParsing(0)) + { + cerr << "ERROR: failed to initialize options" << endl; + exit(-1); + } + + IPCore::FormatIPNode::defaultResampleMethod = resampleMethod; + inputFiles = opts.inputFiles; + + // + // Just stop avoidresampling + // + + nosession = 0; + + if (err2out) cerr.rdbuf(cout.rdbuf()); + + if (showVersion) + { + cout << MAJOR_VERSION << "." + << MINOR_VERSION << "." + << REVISION_NUMBER << endl; + + exit(0); + } + + if (outhalf) { outbits = 16; outtype = (char*)"float"; } + if (out8) { outbits = 8; outtype = (char*)"int"; } + if (reallyverbose) verbose = 1; + + if (outrgb) + { + outchmap.clear(); + outchmap.push_back("R"); + outchmap.push_back("G"); + outchmap.push_back("B"); + } + + if ((rysamples || bysamples) && (usamples || vsamples)) + { + cerr << "ERROR: can output Y U V -or- Y RY BY but not both" << endl; + exit(-1); + } + + // + // Banners + // + + TWK_DEPLOY_SHOW_PROGRAM_BANNER(cout); + TWK_DEPLOY_SHOW_COPYRIGHT_BANNER(cout); + TWK_DEPLOY_SHOW_LOCAL_BANNER(cout); + + // + // Get CPU info and tell the EXR library to use them all + // + + + if (opts.exrcpus > 1) Imf::setGlobalThreadCount(opts.exrcpus); + + string initPath = bundle.rcfile("rviorc", "mu", "RVIO_INIT"); + bundle.addPathToEnvVar("OIIO_LIBRARY_PATH", bundle.appPluginPath("OIIO")); + if (initscript) initPath = initscript; + + try + { + TwkFB::loadProxyPlugins("TWK_FB_PLUGIN_PATH"); + TwkMovie::loadProxyPlugins("TWK_MOVIE_PLUGIN_PATH"); + } + catch (...) + { + cerr << "WARNING: a problem occured while loading image plugins." << endl; + cerr << " some plugins may not have been loaded." << endl; + } + + TwkMovie::GenericIO::addPlugin(new MovieRVIO()); + TwkMovie::GenericIO::addPlugin(new MovieFBIO()); + TwkMovie::GenericIO::addPlugin(new MovieProceduralIO()); + TwkMovie::GenericIO::addPlugin(new MovieNullIO()); + + // + // Compile the list of sequencable file extensions + // + + TwkFB::GenericIO::compileExtensionSet(predicateFileExtensions()); + + IPCore::Application app; + + // + // Initialize everything + // + + try + { + TwkApp::initMu(0); + TwkApp::initPython(); + RVIO::initUICommands(TwkApp::muContext()); + Rv::initCommands(TwkApp::muContext()); + } + catch (const exception &e) + { + cerr << "ERROR: during initialization: " << e.what() << endl; + exit( -1 ); + } + +#ifndef PLATFORM_WINDOWS + if (signal(SIGINT, control_c_handler) == SIG_ERR) + { + cout << "ERROR: failed to install SIGINT signal handler" << endl; + } +#endif + + if (showFormats) + { + TwkMovie::GenericIO::outputFormats(); + exit(0); + } + + if (inputFiles.empty()) + { + cerr << "ERROR: no input files specified" << endl; + exit(-1); + } + + if (!outputFile) + { + cerr << "ERROR: no output file/sequence specified" << endl; + exit(-1); + } + + string outfile = pathConform(outputFile); + + // + // Set up Mu context and process. These will be shared by all of + // the drawing functions. (Allows for more hacking if you can set + // state and use it later). + // + + Mu::MuLangContext* context = TwkApp::muContext(); + Mu::Process* process = TwkApp::muProcess(); + + TwkUtil::setThreadName("RVIO Main"); + + // + // Main + // + + try + { + // If this is just a session dump then handle that first + if (extension(outfile) == "rv") + { + writeSession(outfile); + return 0; + } + + FrameBufferIO::ReadRequest readRequest; + MovieWriter::WriteRequest writeRequest; + + writeRequest.threads = wthreads; + writeRequest.fps = outfps; + writeRequest.compression = codec; + writeRequest.codec = codec; + writeRequest.audioCodec = audioCodec; + writeRequest.quality = quality; + writeRequest.pixelAspect = pixelAspect; + writeRequest.verbose = verbose; + writeRequest.audioChannels = audioChannels; + writeRequest.audioRate = audioRate; + writeRequest.stereo = !strcmp(outStereo, "separate"); + writeRequest.comments = comment; + writeRequest.copyright = copyright; + writeRequest.args = writerArgs; + + copy(outparams.begin(), + outparams.end(), + back_inserter(writeRequest.parameters)); + + if (timerange) + { + writeRequest.timeRangeOverride = true; + writeRequest.frames = frameRange(timerange); + + if (writeRequest.frames.empty()) + { + cerr << "ERROR: timerange \"" + << timerange + << "\" expands to nothing" + << endl; + + exit(-1); + } + } + + if (rysamples || usamples) + { + writeRequest.preferCommonFormat = false; + writeRequest.keepPlanar = true; + } + + vector inputMovies; + MovieWriter::Frames outFrames; + + { + Mu::Process* p = TwkApp::muProcess(); + inputMovies.push_back(makeMovieTree(writeRequest, context, p)); + outFrames = writeRequest.frames; + } + + TwkMovie::Movie* outmov = 0; + +#if 1 +#ifdef WIN32 + GC_INIT(); + GC_allow_register_threads(); + outmov = new ThreadedMovie(inputMovies, outFrames, 8, 0, threadedMovieInit); +#else + ThreadedMovie::ThreadAPI threadAPI; + threadAPI.create = GC_pthread_create; + threadAPI.join = GC_pthread_join; + threadAPI.detach = GC_pthread_detach; + outmov = new ThreadedMovie(inputMovies, outFrames, 8, &threadAPI, threadedMovieInit); +#endif +#endif + + //assert(inputMovies.size() == 1); + //outmov = inputMovies.front(); + + // + // On windows, we need to do this *after* the threaded movie + // has been created. Not sure why. + // + + TwkApp::initWithFile(TwkApp::muContext(), + TwkApp::muProcess(), + TwkApp::muModuleList(), + initPath.c_str()); + + // + // Since the sessions we're created earlier (before we + // called initWithFile), we call postInitialize on each + // thread's session here. + // + // Note that these sesssions may have "extra" sources in + // them representing the leader/overlay movies. + // + for (int i = 0; i < IPCore::App()->documents().size(); ++i) + { + if (Rv::RvSession *s = dynamic_cast (IPCore::App()->documents()[i])) + { + s->postInitialize(); + // + // We show the user no cache stats, so don't bother to compute them. + // + s->graph().cache().setCacheStatsDisabled(true); + } + } + + MovieWriter* writer = TwkMovie::GenericIO::movieWriter(pathConform(outfile)); + + if (!writer) + { + cerr << "ERROR: cannot find a way to write " << outfile << endl; + exit(-1); + } + + // + // Write it. + // + // NOTE: some formats will automatically do color conversion + // to move the pixels into the right colorspace. Since we + // already transformed the image into linear non-planar none + // of that should be happening. + // + + if (verbose) + { + cout << "INFO: writing " << outfile << endl; + + if (!writeRequest.preferCommonFormat && reallyverbose) + { + cout << "INFO: request non-common format" << endl; + } + + if (writeRequest.compression != "") + { + cout << "INFO: output compressor " << writeRequest.compression << endl; + } + + if (writeRequest.codec != "") + { + cout << "INFO: output codec " << writeRequest.codec << endl; + } + + if (reallyverbose) + { + cout << "INFO: output quality " << writeRequest.quality << endl; + } + + if (writeRequest.timeRangeOverride && timerange) + { + cout << "INFO: override time range " << timerange + << ", (" << writeRequest.frames.size() + << " frames)" + << endl; + } + + if (writeRequest.keepPlanar && reallyverbose) + { + cout << "INFO: request planar output if possible" << endl; + } + } + + // + // Tell the writer to do its business + // + + if (!writer->write(outmov, outfile, writeRequest)) exit (-2); + } + catch (TwkExc::Exception& exc) + { + cerr << exc << endl; + exit(-1); + } + catch (exception& exc) + { + cerr << "ERROR: " << exc.what() << endl; + exit(-1); + } + catch (...) + { + cerr << "ERROR: uncaught exception" << endl; + exit(-1); + } + +#ifdef PTW32_STATIC_LIB + pthread_win32_thread_detach_np(); + pthread_win32_process_detach_np(); +#endif + + TwkFB::ThreadPool::shutdown(); + return 0; +} + diff --git a/src/bin/imgtools/rvio/rvio.wrapper b/src/bin/imgtools/rvio/rvio.wrapper new file mode 100755 index 000000000..65390b49e --- /dev/null +++ b/src/bin/imgtools/rvio/rvio.wrapper @@ -0,0 +1,50 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the APP_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set APP_HOME if invoked from a link to the script +# and APP_HOME is not already set +# + +set noglob +set name = rvio + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +#echo RV_HOME = $RV_HOME +set platform = i386 +set bin = "$RV_HOME/bin/$name.bin.$platform" + +if (! (-e $bin) ) then + set bin = "$RV_HOME/bin/$name.bin" +endif + +if (! (-e $bin) ) then + echo "ERROR: binary " $bin " not found" + exit(-1) +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec binary + +exec $bin $*:q diff --git a/src/bin/imgtools/rvio/utf8Main.cpp b/src/bin/imgtools/rvio/utf8Main.cpp new file mode 100644 index 000000000..69e097cc9 --- /dev/null +++ b/src/bin/imgtools/rvio/utf8Main.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.cpp" \ No newline at end of file diff --git a/src/bin/imgtools/rvls/CMakeLists.txt b/src/bin/imgtools/rvls/CMakeLists.txt new file mode 100644 index 000000000..a7d4c9232 --- /dev/null +++ b/src/bin/imgtools/rvls/CMakeLists.txt @@ -0,0 +1,66 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "rvls" +) + +LIST(APPEND _sources main.cpp utf8Main.cpp) + +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +FIND_PACKAGE( + Qt5 + COMPONENTS Core + REQUIRED +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE IOproxy + OpenEXR::OpenEXR + MovieFB + TwkFB + MovieProcedural + MovieProxy + TwkDeploy + TwkMovie + TwkContainer + TwkUtil + arg + stl_ext + Boost::headers + yaml_cpp +) + +IF(RV_TARGET_DARWIN) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE DarwinBundle + ) +ELSE() + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE QTBundle + ) +ENDIF() + +TARGET_COMPILE_OPTIONS( + ${_target} + PRIVATE "-DGIT_HEAD=\"${RV_GIT_COMMIT_SHORT_HASH}\"" "-DRELEASE_DESCRIPTION=\"${RV_RELEASE_DESCRIPTION}\"" +) + +RV_STAGE(TYPE "EXECUTABLE_WITH_PLUGINS" TARGET ${_target}) diff --git a/src/bin/imgtools/rvls/main.cpp b/src/bin/imgtools/rvls/main.cpp new file mode 100644 index 000000000..7bba1f9d5 --- /dev/null +++ b/src/bin/imgtools/rvls/main.cpp @@ -0,0 +1,800 @@ +//****************************************************************************** +// Copyright (c) 2007 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** + +#include "../../utf8Main.h" + +#ifdef PLATFORM_DARWIN +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern void TwkMovie_GenericIO_setDebug(bool); +extern void TwkFB_GenericIO_setDebug(bool); + +using namespace std; +using namespace TwkUtil; +using namespace TwkFB; +using namespace TwkMovie; +using namespace TwkContainer; +using namespace boost; + +bool bruteForce = false; + +vector inputFiles; +int parseInFiles(int argc, char *argv[]) +{ + for (int i=0; i& parts) +{ + if (MovieReader* reader = TwkMovie::GenericIO::movieReader(seq, bruteForce)) + { + try + { + reader->open(seq); + } + catch (std::exception& exc) + { + cerr << "ERROR: " << exc.what() << endl; + } + + MovieInfo info = reader->info(); + ostringstream str; + + if (info.video) + { + str << info.width; parts.push_back(str.str()); str.str(""); + str << info.height; parts.push_back(str.str()); str.str(""); + int nc = info.numChannels; + + switch (info.dataType) + { + case TwkFB::FrameBuffer::UCHAR: str << "8i"; break; + case TwkFB::FrameBuffer::USHORT: str << "16i"; break; + case TwkFB::FrameBuffer::HALF: str << "16f"; break; + case TwkFB::FrameBuffer::FLOAT: str << "32f"; break; + case TwkFB::FrameBuffer::UINT: str << "32i"; break; + case TwkFB::FrameBuffer::DOUBLE: str << "64i"; break; + case TwkFB::FrameBuffer::PACKED_Cb8_Y8_Cr8_Y8: str << "8i"; + nc = 3; + break; + case TwkFB::FrameBuffer::PACKED_Y8_Cb8_Y8_Cr8: str << "8i"; + nc = 3; + break; + case TwkFB::FrameBuffer::PACKED_R10_G10_B10_X2: + case TwkFB::FrameBuffer::PACKED_X2_B10_G10_R10: + str << "10i"; + nc = 3; + break; + default: + str << info.dataType; + } + + parts.push_back(str.str()); str.str(""); + str << nc; parts.push_back(str.str()); str.str(""); + int nframes = info.end - info.start + 1; + + if (nframes > 0) + { + str << info.fps; parts.push_back(str.str()); str.str(""); + str << nframes; parts.push_back(str.str()); str.str(""); + } + else + { + parts.push_back(""); + parts.push_back(""); + } + } + else + { + for (int i=0; i < 6; i++) + parts.push_back(""); + } + + if (info.audio) + { + if (info.audioChannels.size() == 0) + { + str << "-"; parts.push_back(str.str()); str.str(""); + } + else + { + str << info.audioChannels.size(); parts.push_back(str.str()); str.str(""); + } + } + else + { + for (int i=0; i < 1; i++) + parts.push_back(""); + } + + delete reader; + } + else if (seq.substr(0,14) == "RVImageSource|") + { + ostringstream str; + vector strs; + boost::split(strs,seq,boost::is_any_of("|")); + parts.push_back(strs[3]); // width + parts.push_back(strs[4]); // height + parts.push_back(strs[8] + ((strs[9] == "0") ? "i" : "f")); // depth+type + + str << strs[2].size(); // numChannels + parts.push_back(str.str()); + + string fps = "", frames = ""; + int nframes = atoi(strs[6].c_str()) - atoi(strs[5].c_str()) + 1; + if (nframes > 0) + { + fps = strs[11]; + str.clear(); + str.str(""); + str << nframes; + frames = str.str(); + } + parts.push_back(fps); // fps + parts.push_back(frames); // frame count + + // no audio + for (int i=0; i < 1; i++) parts.push_back(""); + } + + parts.push_back(seq); +} + +string lsAlignedLongOuput(vector >& listing, bool x2=true) +{ + ostringstream out; + vector spacing; + if (listing.empty()) return ""; + spacing.resize(listing.front().size()); + + for (int i=0; i < spacing.size(); i++) + { + for (int q=1; q < listing.size(); q++) + { + if (listing[q].size() > 1) + spacing[i] = max(spacing[i], listing[q][i].size()); + } + } + + for (int i=0; i < spacing.size(); i++) + { + for (int q=0; q < listing.size(); q++) + { + if (listing[q].size() > 1) + if (spacing[i] > 0) spacing[i] = max(spacing[i], listing[q][i].size()); + } + } + + out << setfill(' '); + + for (int q=0; q < listing.size(); q++) + { + const vector& entry = listing[q]; + + if (entry.size() > 1) + { + for (int i=0; i < entry.size(); i++) + { + if (spacing[i] == 0) continue; + if (i == spacing.size()-1 || i == 1 || i == 3) out << left; + else out << right; + + if (x2 && i == 1 && entry[i] != "") out << " x "; else out << " "; + + if (i == entry.size() - 1) out << entry[i]; + else out << setw(spacing[i]) << entry[i] << setw(0); + } + + out << endl; + } + } + + return out.str(); +} + +string lsExtended(const string& seq, bool yaml=false) +{ + YAML::Emitter out_yaml; + out_yaml.SetIndent(4); + out_yaml << YAML::BeginMap; // Outter Map + out_yaml << YAML::Key << seq; + out_yaml << YAML::Value << YAML::BeginMap; // Sequence Map + ostringstream out; + if (MovieReader* reader = TwkMovie::GenericIO::movieReader(seq, bruteForce)) + { + try + { + reader->open(seq); + } + catch (std::exception& exc) + { + cerr << "ERROR: " << exc.what() << endl; + } + + const MovieInfo& minfo = reader->info(); + const FrameBuffer& fb = minfo.proxy; + + const FrameBuffer::AttributeVector& attrs = fb.attributes(); + vector > listing; + + out_yaml << YAML::Key << "Attributes"; + out_yaml << YAML::Value << YAML::BeginMap; //Attribute Map + for (int i=0; i < attrs.size(); i++) + { + listing.resize(listing.size()+1); + listing.back().resize(2); + + listing.back()[0] = attrs[i]->name(); + listing.back()[1] = attrs[i]->valueAsString(); + + out_yaml << YAML::Key << attrs[i]->name(); + out_yaml << YAML::Value << attrs[i]->valueAsString(); + } + out_yaml << YAML::EndMap; // Attribute Map End + + + if (minfo.video) + { + ostringstream str; + listing.resize(listing.size()+1); + listing.back().resize(2); + listing.back()[0] = "Channels"; + + out_yaml << YAML::Key << "Channels"; + out_yaml << YAML::Value << YAML::BeginSeq; //Channel Sequencce + + for (int i=0; i < minfo.channelInfos.size(); i++) + { + if (i) str << ", "; + str << minfo.channelInfos[i].name; + out_yaml << minfo.channelInfos[i].name; + } + out_yaml << YAML::EndSeq; //Channel Sequence End + + listing.back()[1] = str.str(); + + str.str(""); + listing.resize(listing.size()+1); + listing.back().resize(2); + listing.back()[0] = "Resolution"; + + str << minfo.uncropWidth << " x " << minfo.uncropHeight << ", "; + str << minfo.numChannels << "ch, "; + + out_yaml << YAML::Key << "Resolution"; + out_yaml << YAML::Value << YAML::BeginMap; //Resolution Map + + out_yaml << YAML::Key << "Width"; + out_yaml << YAML::Value << minfo.uncropWidth; + + out_yaml << YAML::Key << "Height"; + out_yaml << YAML::Value << minfo.uncropHeight; + + out_yaml << YAML::Key << "Channels"; + out_yaml << YAML::Value << minfo.numChannels; + + out_yaml << YAML::Key << "Depth"; + out_yaml << YAML::Value << YAML::BeginMap; // Depth Map + switch (minfo.dataType) + { + case TwkFB::FrameBuffer::UCHAR: + str << "8 bits/ch"; + out_yaml << YAML::Key << "Bits"; + out_yaml << YAML::Value << 8; + out_yaml << YAML::Key << "Type"; + out_yaml << YAML::Value << "int"; + break; + case TwkFB::FrameBuffer::USHORT: + str << "16 bits/ch"; + out_yaml << YAML::Key << "Bits"; + out_yaml << YAML::Value << 16; + out_yaml << YAML::Key << "Type"; + out_yaml << YAML::Value << "int"; + break; + case TwkFB::FrameBuffer::HALF: + str << "16 bits/ch floating point"; + out_yaml << YAML::Key << "Bits"; + out_yaml << YAML::Value << 16; + out_yaml << YAML::Key << "Type"; + out_yaml << YAML::Value << "float"; + break; + case TwkFB::FrameBuffer::FLOAT: + str << "32 bits/ch floating point"; + out_yaml << YAML::Key << "Bits"; + out_yaml << YAML::Value << 32; + out_yaml << YAML::Key << "Type"; + out_yaml << YAML::Value << "float"; + break; + case TwkFB::FrameBuffer::UINT: + str << "32 bits/ch"; + out_yaml << YAML::Key << "Bits"; + out_yaml << YAML::Value << 32; + out_yaml << YAML::Key << "Type"; + out_yaml << YAML::Value << "int"; + break; + case TwkFB::FrameBuffer::DOUBLE: + str << "64 bits/ch floating point"; + out_yaml << YAML::Key << "Bits"; + out_yaml << YAML::Value << 64; + out_yaml << YAML::Key << "Type"; + out_yaml << YAML::Value << "float"; + break; + case TwkFB::FrameBuffer::PACKED_Cb8_Y8_Cr8_Y8: + case TwkFB::FrameBuffer::PACKED_Y8_Cb8_Y8_Cr8: + str << "8 bits/ch"; + out_yaml << YAML::Key << "Bits"; + out_yaml << YAML::Value << 8; + out_yaml << YAML::Key << "Type"; + out_yaml << YAML::Value << "int"; + break; + case TwkFB::FrameBuffer::PACKED_R10_G10_B10_X2: + case TwkFB::FrameBuffer::PACKED_X2_B10_G10_R10: + str << "10 bits/ch"; + out_yaml << YAML::Key << "Bits"; + out_yaml << YAML::Value << 10; + out_yaml << YAML::Key << "Type"; + out_yaml << YAML::Value << "int"; + break; + default: + str << "?"; + out_yaml << YAML::Key << "Bits"; + out_yaml << YAML::Value << "?"; + out_yaml << YAML::Key << "Type"; + out_yaml << YAML::Value << "?"; + } + out_yaml << YAML::EndMap; // Depth Map End + out_yaml << YAML::EndMap; // Resolution Map End + + listing.back()[1] = str.str(); + } + + + reverse(listing.begin(), listing.end()); + + out << seq << ":" << endl << endl; + out << lsAlignedLongOuput(listing, false); + out << endl << endl; + + delete reader; + } + else if (seq.substr(0,14) == "RVImageSource|") + { + vector strs; + boost::split(strs,seq,boost::is_any_of("|")); + + vector > listing; + ostringstream str; + listing.resize(listing.size()+1); + listing.back().resize(2); + listing.back()[0] = "Channels"; + + out_yaml << YAML::Key << "Channels"; + out_yaml << YAML::Value << YAML::BeginSeq; // Channel Sequence + + for (int i=0; i < strs[2].size(); i++) + { + if (i) str << ", "; + str << strs[2][i]; + out_yaml << strs[2][i]; + } + + out_yaml << YAML::EndSeq; // End Channel Sequence + + listing.back()[1] = str.str(); + + str.str(""); + listing.resize(listing.size()+1); + listing.back().resize(2); + listing.back()[0] = "Resolution"; + + str << strs[3] << " x " << strs[4] << ", "; + str << strs[2].size() << "ch, "; + str << strs[8] << " bits/ch" << ((strs[9] == "0") ? "" : " floating point"); + + out_yaml << YAML::Key << "Resolution"; + out_yaml << YAML::Value << YAML::BeginMap; // Resolution Map + + out_yaml << YAML::Key << "Width"; + out_yaml << YAML::Value << strs[3]; + + out_yaml << YAML::Key << "Height"; + out_yaml << YAML::Value << strs[4]; + + out_yaml << YAML::Key << "Channels"; + out_yaml << YAML::Value << strs[2].size(); + + out_yaml << YAML::Key << "Depth"; + out_yaml << YAML::Value << YAML::BeginMap; // Depth Map + + out_yaml << YAML::Key << "Bits"; + out_yaml << YAML::Value << strs[8]; + out_yaml << YAML::Key << "Type"; + out_yaml << YAML::Value << ((strs[9] == "0") ? "int" : "float"); + out_yaml << YAML::EndMap; // End Depth Map + out_yaml << YAML::EndMap; // End Resolution Map + + + listing.back()[1] = str.str(); + + reverse(listing.begin(), listing.end()); + + out << seq << ":" << endl << endl; + out << lsAlignedLongOuput(listing, false); + out << endl << endl; + } + + out_yaml << YAML::EndMap; // Sequence Map + out_yaml << YAML::EndMap; // Outter Map + out_yaml << YAML::Newline; + + return yaml ? out_yaml.c_str() : out.str(); +} + +vector readSession(string path) +{ + vector allfiles; + string thispath; + GTOReader reader = new GTOReader(); + GTOReader::Containers nodes = reader.read(path.c_str()); + for (int n=0; n < nodes.size(); n++) + { + if (nodes[n]->protocol() == "RVFileSource") + { + PropertyContainer::Components comps = nodes[n]->components(); + for (int c=0; c < comps.size(); c++) + { + if (comps[c]->name() != "media") continue; + + Component::Container props = comps[c]->properties(); + for (int p=0; p < props.size(); p++) + { + if (props[p]->name() != "movie") continue; + + vector movs; + string movs_string = props[p]->valueAsString(); + split(movs, movs_string, is_any_of(string(" "))); + for (int m=0; m < movs.size(); m++) + { + thispath = movs[m]; + if (movs[m].size() > 13 && movs[m].substr(0, 14) == "${RV_PATHSWAP_") + { + int endpos = movs[m].find("}"); + if (endpos == string::npos) continue; + string envvar = movs[m].substr(2, endpos - 2); + char* pathswap = getenv(envvar.c_str()); + if (pathswap != NULL) + { + thispath.replace(0, endpos + 1, pathswap); + } + } + } + } + } + allfiles.push_back(thispath); + } + else if (nodes[n]->protocol() == "RVImageSource") + { + ostringstream imgsrc; + string name = "", channels = ""; + int uncropWidth = 0, uncropHeight = 0, + start = 0, end = 0, inc = 0, + bitsPerChannel = 0, isFloat = 0; + float pixelAspect = 0.0, fps = 0.0; + PropertyContainer::Components comps = nodes[n]->components(); + for (int c=0; c < comps.size(); c++) + { + if (comps[c]->name() != "media" && comps[c]->name() != "image") continue; + + Component::Container props = comps[c]->properties(); + for (int p=0; p < props.size(); p++) + { + string pName = props[p]->name(); + if (pName == "name") name = props[p]->valueAsString(); + else if (pName == "channels") channels = props[p]->valueAsString(); + else if (pName == "uncropWidth") uncropWidth = reinterpret_cast(props[p])->front(); + else if (pName == "uncropHeight") uncropHeight = reinterpret_cast(props[p])->front(); + else if (pName == "start") start = reinterpret_cast(props[p])->front(); + else if (pName == "end") end = reinterpret_cast(props[p])->front(); + else if (pName == "inc") inc = reinterpret_cast(props[p])->front(); + else if (pName == "bitsPerChannel") bitsPerChannel = reinterpret_cast(props[p])->front(); + else if (pName == "float") isFloat = reinterpret_cast(props[p])->front(); + else if (pName == "pixelAspect") pixelAspect = reinterpret_cast(props[p])->front(); + else if (pName == "fps") fps = reinterpret_cast(props[p])->front(); + } + } + imgsrc << "RVImageSource|" + << name << "|" + << channels << "|" + << uncropWidth << "|" + << uncropHeight << "|" + << start << "|" + << end << "|" + << inc << "|" + << bitsPerChannel << "|" + << isFloat << "|" + << pixelAspect << "|" + << fps << "|"; + + thispath = imgsrc.str(); + imgsrc.str(""); + allfiles.push_back(thispath); + } + } + return allfiles; +} + +int utf8Main(int argc, char** argv) +{ + TwkFB::ThreadPool::initialize(); + +#ifdef PLATFORM_DARWIN + TwkApp::DarwinBundle bundle("RV", MAJOR_VERSION, MINOR_VERSION, REVISION_NUMBER); +#else + QCoreApplication qapp(argc, argv); + TwkApp::QTBundle bundle("rv", MAJOR_VERSION, MINOR_VERSION, REVISION_NUMBER); + (void) bundle.top(); +#endif + + int showVersion = 0; + int a = 0; + int s = 0; + int b = 0; + int minseq = 3; + int nr = 0; + int l = 0; + int x = 0; + int ns = 0; + int showFormats = 0; + int yaml = 0; + char* debugString = 0; + char* outputFile = 0; + + // + // Call the deploy functions + // + + TWK_DEPLOY_APP_OBJECT dobj(MAJOR_VERSION, + MINOR_VERSION, + REVISION_NUMBER, + argc, argv, + RELEASE_DESCRIPTION, + "HEAD=" GIT_HEAD); + + if (arg_parse + (argc, argv, + "", "\nUsage: rvls list movies and image sequences\n", + "", ARG_SUBR(parseInFiles), "Input sequence patterns, images, movies, or directories ", + "-a", ARG_FLAG(&a), "Show hidden files", + "-s", ARG_FLAG(&s), "Show sequences only (no non-sequence member files)", + "-l", ARG_FLAG(&l), "Show long listing", + "-x", ARG_FLAG(&x), "Show extended attributes and image structure", + "-b", ARG_FLAG(&b), "Use brute force if no reader found", + "-o %S", &outputFile, "Output log file. Results will be printed to stdout by default", + "-nr", ARG_FLAG(&nr), "Do not show frame ranges", + "-ns", ARG_FLAG(&ns), "Do not infer sequences (list each file separately)", + "-min %d", &minseq, "Minimum number of files considered a sequence (default=%d)", minseq, + "-formats", ARG_FLAG(&showFormats), "List image/movie formats", + "-yaml", ARG_FLAG(&yaml), "Output in YAML format. (-x only)", + "-version", ARG_FLAG(&showVersion), "Show rvls version number", + "-debug %S", &debugString, "Debug category (only 'plugins' for now)", + NULL) < 0) + { + exit(-1); + } + + bruteForce = b ? true : false; + bool nonmatching = !s ? true : false; + bool showranges = nr ? false : true; + + try + { + debugSwitches(debugString); + } + catch (const std::exception &e) + { + cerr << "ERROR: during initialization: " << e.what() << endl; + exit( -1 ); + } + + if (showVersion) + { + cout << MAJOR_VERSION << "." + << MINOR_VERSION << "." + << REVISION_NUMBER << endl; + + exit(0); + } + + bundle.addPathToEnvVar("OIIO_LIBRARY_PATH", bundle.appPluginPath("OIIO")); + TwkApp::Bundle::PathVector licfiles = bundle.licenseFiles("license", "gto"); + + try + { + TwkFB::loadProxyPlugins("TWK_FB_PLUGIN_PATH"); + TwkMovie::loadProxyPlugins("TWK_MOVIE_PLUGIN_PATH"); + } + catch (...) + { + cerr << "WARNING: a problem occured while loading image plugins." << endl; + cerr << " some plugins may not have been loaded." << endl; + } + + ostringstream out; + + TwkMovie::GenericIO::addPlugin(new MovieFBIO()); + TwkMovie::GenericIO::addPlugin(new MovieProceduralIO()); + + if (showFormats) + { + TwkMovie::GenericIO::outputFormats(); + exit(0); + } + + TwkFB::GenericIO::compileExtensionSet(predicateFileExtensions()); + + if (inputFiles.empty()) + { + inputFiles.push_back("."); + } + + try + { + vector allfiles; + + for (int i=0; i < inputFiles.size(); i++) + { + string path = inputFiles[i]; + + if (isDirectory(path.c_str())) + { + if (path[path.size()-1] != '/') path.append("/"); + vector files; + + if (filesInDirectory(path.c_str(), files)) + { + for (int q=0; q < files.size(); q++) + { + if (files[q].size() && files[q][0] == '.' && !a) continue; + string file = path; + file += files[q]; + allfiles.push_back(file); + } + } + } + else if (path.size() > 2 && path.substr(path.size() - 3, path.size() - 1) == ".rv") + { + allfiles = readSession(path); + } + else + { + allfiles.push_back(path); + } + } + + SequenceNameList seqs; + + if (ns) + { + seqs = allfiles; + } + else + { + SequencePredicate sPred = (bruteForce) ? + AnySequencePredicate : GlobalExtensionPredicate; + seqs = sequencesInFileList(allfiles, + sPred, + nonmatching, + showranges, + minseq); + } + std::sort(seqs.begin(), seqs.end()); + + + if (l) + { + vector > listing(seqs.size()+1); + listing.front().push_back("w"); + listing.front().push_back("h"); + listing.front().push_back("typ"); + listing.front().push_back("#ch"); + listing.front().push_back("fps"); + listing.front().push_back("#fr"); + listing.front().push_back("#ach"); + listing.front().push_back("file"); + + for (int i=0; i < seqs.size(); i++) + { + lsLong(seqs[i], listing[i+1]); + } + + out << lsAlignedLongOuput(listing); + } + else if (x) + { + for (int i=0; i < seqs.size(); i++) + { + out << lsExtended(seqs[i], yaml); + } + } + else + { + copy(seqs.begin(), seqs.end(), ostream_iterator(out, "\n")); + } + } + catch (std::exception& exc) + { + cerr << "ERROR: " << exc.what() << endl; + exit(-1); + } + catch (...) + { + cerr << "ERROR: uncaught exception" << endl; + exit(-1); + } + + if (outputFile) + { + ofstream output(outputFile); + output << out.str(); + output.close(); + } + else + { + cout << out.str(); + } + + TwkFB::ThreadPool::shutdown(); + + return 0; +} diff --git a/src/bin/imgtools/rvls/rvls.wrapper b/src/bin/imgtools/rvls/rvls.wrapper new file mode 100755 index 000000000..57bd3e56b --- /dev/null +++ b/src/bin/imgtools/rvls/rvls.wrapper @@ -0,0 +1,49 @@ +#!/bin/tcsh -f + +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# This script sets the APP_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set APP_HOME if invoked from a link to the script +# and APP_HOME is not already set +# + +set noglob +set name = rvls + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +set platform = i386 +set bin = "$RV_HOME/bin/$name.bin.$platform" + +if (! (-e $bin) ) then + set bin = "$RV_HOME/bin/$name.bin" +endif + +if (! (-e $bin) ) then + echo "ERROR: binary " $bin " not found" + exit(-1) +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec binary + +exec $bin $*:q diff --git a/src/bin/imgtools/rvls/utf8Main.cpp b/src/bin/imgtools/rvls/utf8Main.cpp new file mode 100644 index 000000000..69e097cc9 --- /dev/null +++ b/src/bin/imgtools/rvls/utf8Main.cpp @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include "../../utf8Main.cpp" \ No newline at end of file diff --git a/src/bin/mu/CMakeLists.txt b/src/bin/mu/CMakeLists.txt new file mode 100644 index 000000000..d21cc3e26 --- /dev/null +++ b/src/bin/mu/CMakeLists.txt @@ -0,0 +1,6 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +ADD_SUBDIRECTORY(mu-interp) diff --git a/src/bin/mu/mu-interp/CMakeLists.txt b/src/bin/mu/mu-interp/CMakeLists.txt new file mode 100644 index 000000000..12ebfda0f --- /dev/null +++ b/src/bin/mu/mu-interp/CMakeLists.txt @@ -0,0 +1,127 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "mu-interp" +) + +LIST(APPEND _sources main.cpp) + +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE arg Mu MuLang stl_ext +) + +IF(RV_TARGET_DARWIN) + TARGET_COMPILE_OPTIONS( + ${_target} + PRIVATE "-DMU_USE_READLINE" + ) + + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE readline BDWGC::Gc + ) +ELSEIF(RV_TARGET_LINUX) + TARGET_COMPILE_OPTIONS( + ${_target} + PRIVATE "-DMU_USE_READLINE" + ) + + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE readline BDWGC::Gc + ) +ELSEIF(RV_TARGET_WINDOWS) + TARGET_COMPILE_OPTIONS( + ${_target} + PRIVATE "-DLINKED_MODULES" + ) + + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE MuAutoDoc + MuSystem + MuIO + MuImage + MuEncoding + MuQt5 + gc + ) +ENDIF() + +RV_STAGE(TYPE "EXECUTABLE_WITH_PLUGINS" TARGET ${_target}) + +ADD_TEST( + NAME "mu-interp" + COMMAND ${CMAKE_COMMAND} -E env QT_QPA_PLATFORM=minimal "$" -help +) + +FILE(GLOB mu_test_files test/*.mu) + +# Drop any mu script in that +FOREACH( + _mu_test_file + ${mu_test_files} +) + GET_FILENAME_COMPONENT(mu_test_file ${_mu_test_file} ABSOLUTE) + GET_FILENAME_COMPONENT(mu_test_file_name ${_mu_test_file} NAME) + + SET(_test_name + "mu-interp ${mu_test_file_name}" + ) + + SET(SKIP_THIS_MU_TEST + FALSE + ) + + # TODO: This test currently fails on Windows+Debug + IF(RV_TARGET_WINDOWS + AND ${CMAKE_BUILD_TYPE} STREQUAL "Debug" + ) + IF(mu_test_file_name STREQUAL "case.mu") + MESSAGE(STATUS "Skipping ${mu_test_file_name} on Windows") + SET(SKIP_THIS_MU_TEST + TRUE + ) + ENDIF() + ENDIF() + + IF(NOT SKIP_THIS_MU_TEST) + IF(RV_TARGET_WINDOWS) + # The Windows version of Mu environment loader uses ';' to split entries. Most likely to cope with 'C:\' like syntax. See + # 'src\lib\mu\Mu\Environment.cpp:init' static method. Then, on top of that, we can't easily use the ';' character with CMake command as it gets + # interpreted to split statements. We then resolve to a plain 'bash -c' invocation. + SET(_mu_module_path + "\"${CMAKE_CURRENT_SOURCE_DIR}/test;${RV_STAGE_PLUGINS_MU_DIR}\"" + ) + ADD_TEST( + NAME ${_test_name} + COMMAND bash -c "export MU_MODULE_PATH=\"${_mu_module_path}\";QT_QPA_PLATFORM=minimal; \"$\" -main ${mu_test_file}" + ) + ELSE() + # macOS & Linux + ADD_TEST( + NAME ${_test_name} + COMMAND ${CMAKE_COMMAND} -E env MU_MODULE_PATH=${CMAKE_CURRENT_SOURCE_DIR}/test:${RV_STAGE_PLUGINS_MU_DIR} QT_QPA_PLATFORM=minimal + "$" -main ${mu_test_file} + ) + ENDIF() + ENDIF() + +ENDFOREACH() diff --git a/src/bin/mu/mu-interp/deprecated/generic_qsort.mu b/src/bin/mu/mu-interp/deprecated/generic_qsort.mu new file mode 100644 index 000000000..a82a90f4c --- /dev/null +++ b/src/bin/mu/mu-interp/deprecated/generic_qsort.mu @@ -0,0 +1,50 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +use runtime; + +// +// Generic quicksort +// + +operator: <= (bool; string a, string b) { compare(a, b) <= 0; } +operator: >= (bool; string a, string b) { compare(a, b) >= 0; } + +function: qsort (void; 'a a, int lo, int hi) +{ + if (lo < hi) + { + let l = lo, + h = hi, + p = a[hi]; + + do + { + while (l < h && a[l] <= p) l++; + while (h > l && a[h] >= p) h--; + + if (l < h) + { + let t = a[l]; + a[l] = a[h]; + a[h] = t; + } + + } while (l < h); + + let t = a[l]; + a[l] = a[hi]; + a[hi] = t; + + qsort(a, lo, l-1); + qsort(a, l+1, hi); + } +} + +int[] a = { 10, 1, 2, 9, 4, 3, 6, 5, 8, 7 }; +string[] b = {"hello", "a", "one", "two", "zebra"}; +qsort(a, 0, a.size() - 1); +qsort(b, 0, b.size() - 1); +a; diff --git a/src/bin/mu/mu-interp/future/case.mu b/src/bin/mu/mu-interp/future/case.mu new file mode 100644 index 000000000..a45601b2c --- /dev/null +++ b/src/bin/mu/mu-interp/future/case.mu @@ -0,0 +1,24 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// Case with predicate +// + +case (re, regex.match) +{ + "([0-9]+-[0-9]+)-[0-9]+" -> { ... } + ".*" -> { print("matched anything\n"); } +} + +// +// case_test +// patten_test (test, body) +// patten_test (body) +// patten_test (test, body) +// patten_test (test, body) +// patten_test (body) +// diff --git a/src/bin/mu/mu-interp/future/functional.mu b/src/bin/mu/mu-interp/future/functional.mu new file mode 100644 index 000000000..4b92a61f4 --- /dev/null +++ b/src/bin/mu/mu-interp/future/functional.mu @@ -0,0 +1,54 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// Basic functional mapping operations +// + +Predicate := (bool;'a); + +\: map ('a[]; ('a;'b) f, 'b[] array) +{ + 'a[] result; + for_each (x; array) result.push_back(f(x)); + result; +} + +\: map (void; (void;'b) f, 'b[] array) +{ + for_each (x; array) f(x); +} + +\: reduce ('a; ('a;'a,'a) op, 'a[] array, 'a seed) +{ + let acc = seed; + for_each (x; array) acc = op(acc, x); + acc; +} + +\: filter ('a[]; Predicate pred, 'a[] array) +{ + 'a[] result; + for_each (x; array) if (pred(x)) result.push_back(x); + result; +} + +operator: ! (Predicate; Predicate f) +{ + \: (bool; 'a x) { !f(x); }; +} + +operator: || (Predicate; Predicate f1, Predicate f2) +{ + \: (bool; 'a x) { f1(x) || f2(x); }; +} + +operator: && (Predicate; Predicate f1, Predicate f2) +{ + \: (bool; 'a x) { f1(x) && f2(x); }; +} + + +map(print, string[] {"one\n", "two\n", "three\n"}); diff --git a/src/bin/mu/mu-interp/future/grahamtest1.mu b/src/bin/mu/mu-interp/future/grahamtest1.mu new file mode 100644 index 000000000..07a64efcd --- /dev/null +++ b/src/bin/mu/mu-interp/future/grahamtest1.mu @@ -0,0 +1,15 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// The problem: Write a function foo that takes a number n and returns a +// function that takes a number i, and returns n incremented by i. + +\: foo ('a n) +{ + \: ('a i) { n + i; }; +} + +foo(1); diff --git a/src/bin/mu/mu-interp/future/method_shortcut.mu b/src/bin/mu/mu-interp/future/method_shortcut.mu new file mode 100644 index 000000000..33c352721 --- /dev/null +++ b/src/bin/mu/mu-interp/future/method_shortcut.mu @@ -0,0 +1,27 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// Method short cut. set() is called as Foo.set(this, n). +// +// NOTE: currently there's a problem with reversing the order of +// these functions: +// +// ERROR: No match found for function "set" with 1 argument: Foo +// Option #1: Foo.set (void; Foo this, Foo n) (member) +// test/method_shortcut.mu, line 7, char 35: Cannot resolve call to "set". +// ERROR: attempted call to unresolved function +// + +class: Foo +{ + method: Foo (Foo; Foo n) { set(n); } + method: set (void; Foo n) { next = n; } + + Foo next; +}; + +Foo(Foo(nil)); + diff --git a/src/bin/mu/mu-interp/future/mmult.mu b/src/bin/mu/mu-interp/future/mmult.mu new file mode 100644 index 000000000..97ccf3a66 --- /dev/null +++ b/src/bin/mu/mu-interp/future/mmult.mu @@ -0,0 +1,59 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// Type patterns. Patten matching is depth first. '#N and '#M have +// type "size" or something. The first type pattern determines the +// meanings of the type/index variables. +// + +int 'M, 'R, 'N; +MatrixMR := 'a['M,'R]; +MatrixRN := 'b['R,'N]; +MatrixRR := 'c['M,'N]; + +operator: * (MatrixMN; MatrixMR a, MatrixRN b) +{ + MatrixMN m; + + for (int i = 0; i < 'M; i++) + { + for (int j = 0; j < 'N; j++) + { + 'c acc = 0; + + for (int q = 0; q < 'R; q++) + { + acc += a[i,q] * b[q,j]; + } + + m[i,j] = acc; + } + } + + m; +} + +operator: * (MatrixMN; MatrixMR a, MatrixRN b) +{ + MatrixMN m; + + for_index (i, j; m) + { + 'c acc; + + for_index (q; 4) + { + acc += a[i,q] * b[q,j]; + } + + m[i,j] = acc; + } + + m; +} + +row_type float[4,4]; +column_type float[4,4]; diff --git a/src/bin/mu/mu-interp/future/serialize.mu b/src/bin/mu/mu-interp/future/serialize.mu new file mode 100644 index 000000000..5204f0e4a --- /dev/null +++ b/src/bin/mu/mu-interp/future/serialize.mu @@ -0,0 +1,54 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +use test; +use io; + +a_class a = a_class("A"); +b_class b = b_class("B"); + +b.value = 123.321; +b.ch = 'q'; +a.next = b; +a.value = 69; + +string a_value = string(a); +string b_value = string(b); + +let ofile = ofstream("/tmp/test.muc"); +let n = serialize(ofile, a); +ofile.close(); +n; + +let ifile = ifstream("/tmp/test.muc"); +a_class xa = deserialize(ifile); +b_class xb = xa.next; +ifile.close(); + +assert(string(xa) == a_value); +assert(string(xb) == b_value); + + +a_class root = a_class("ROOT"); +b_class head = b_class("HEAD"); +root.next = head; + +for (int i=0; i < 1000; i++) +{ + b_class b = b_class("NODE " + i); + head.next = b; + b.value = float(100) / 100.0; + b.ch = 'm'; + head = b; +} + +string rootdump = string(root); + +osstream osbuf = osstream(); +serialize(osbuf, root); + +isstream isbuf = isstream(string(osbuf)); +a_class newroot = deserialize(isbuf); +assert(rootdump == string(newroot)); diff --git a/src/bin/mu/mu-interp/future/serialize_funcobj.mu b/src/bin/mu/mu-interp/future/serialize_funcobj.mu new file mode 100644 index 000000000..aa895d5eb --- /dev/null +++ b/src/bin/mu/mu-interp/future/serialize_funcobj.mu @@ -0,0 +1,40 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +use io; + +//let M = \: (float;float a, float b, float c) { b + math.sin(c) + a; }; +//let F = M(,,0)(,68); + +function: doit (float; int a, int b) +{ + let MF = \: (float; float a, float b, float c) + { + let A = \: (float; float a, float b) { a + b; }; + math.sin(c) + A(1,)(2); + }; + + let A = MF(,,0); + A(a, b); +} + +doit(1, 2); + +//let F = A(,68); + +// print(string(F) + "\n"); + +// print("WRITING\n"); +// let ofile = ofstream("/tmp/test.muc"); +// let n = serialize(ofile, F); +// ofile.close(); +// n; + +// print("READING\n"); +// let ifile = ifstream("/tmp/test.muc"); +// (float;float) G = deserialize(ifile); +// ifile.close(); +// G(1.0); + diff --git a/src/bin/mu/mu-interp/future/should_reduce_more.mu b/src/bin/mu/mu-interp/future/should_reduce_more.mu new file mode 100644 index 000000000..744540264 --- /dev/null +++ b/src/bin/mu/mu-interp/future/should_reduce_more.mu @@ -0,0 +1,33 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +F := (float; float); +BinOp := (float;float,float); + +\: ident (float a) { a; } +\: square (float a) { a * a; } + +operator: - (F f) { \: (float a) { -f(a); }; } + +\: op (F fa, F fb, BinOp o) +{ + \: (float a, float b) { o(fa(a),fb(b)); }; +} + +// core dumps if floats, not if ints +//operator: + (F fa, F fb) { op(fa, fb, (+)); } + +operator: + (BinOp; F fa, F fb) { op(fa, fb, (+)); } + +\: foo (F a, F b) { a + -b; } + +// +// Why is this not reducing completely? +// there is still a dynamic thunk call in there. +// + +-(-math.sin);//(1,2); +//foo(ident, square); diff --git a/src/bin/mu/mu-interp/future/unfold.mu b/src/bin/mu/mu-interp/future/unfold.mu new file mode 100644 index 000000000..069fca7ad --- /dev/null +++ b/src/bin/mu/mu-interp/future/unfold.mu @@ -0,0 +1,33 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// Lisp definition +// +// (defun unfold (p f g seed &optional (tail-gen #'identity)) +// (if (funcall p seed) (funcall tail-gen seed) +// (cons (funcall f seed) +// (unfold p f g (funcall g seed))))) + +\: unfold ('a[]; + (bool;'a) p, + ('a;'a) f, + ('a;'a) g, + 'a seed, + ('a;'a) tail = \: ('a;'a a) { a; } ) +{ + p(seed) ? tail(seed) : f(seed) + unfold(p, f, g, g(seed)); +} + +// +// (defun unfold-right (p f g seed &optional (tail nil)) +// (labels ((lp (seed list) +// (if (funcall p seed) list +// (lp (funcall g seed) +// (cons (funcall f seed) list))))) +// (lp seed tail))) +// + diff --git a/src/bin/mu/mu-interp/images/colorwheel.png b/src/bin/mu/mu-interp/images/colorwheel.png new file mode 100644 index 000000000..336201638 Binary files /dev/null and b/src/bin/mu/mu-interp/images/colorwheel.png differ diff --git a/src/bin/mu/mu-interp/images/constant.png b/src/bin/mu/mu-interp/images/constant.png new file mode 100644 index 000000000..3779e69ec Binary files /dev/null and b/src/bin/mu/mu-interp/images/constant.png differ diff --git a/src/bin/mu/mu-interp/images/lambda.png b/src/bin/mu/mu-interp/images/lambda.png new file mode 100644 index 000000000..07f1997da Binary files /dev/null and b/src/bin/mu/mu-interp/images/lambda.png differ diff --git a/src/bin/mu/mu-interp/images/method.png b/src/bin/mu/mu-interp/images/method.png new file mode 100644 index 000000000..24e599f74 Binary files /dev/null and b/src/bin/mu/mu-interp/images/method.png differ diff --git a/src/bin/mu/mu-interp/images/module.png b/src/bin/mu/mu-interp/images/module.png new file mode 100644 index 000000000..92b973196 Binary files /dev/null and b/src/bin/mu/mu-interp/images/module.png differ diff --git a/src/bin/mu/mu-interp/images/mu.png b/src/bin/mu/mu-interp/images/mu.png new file mode 100644 index 000000000..c7647832c Binary files /dev/null and b/src/bin/mu/mu-interp/images/mu.png differ diff --git a/src/bin/mu/mu-interp/images/mu_big.png b/src/bin/mu/mu-interp/images/mu_big.png new file mode 100644 index 000000000..de5597c80 Binary files /dev/null and b/src/bin/mu/mu-interp/images/mu_big.png differ diff --git a/src/bin/mu/mu-interp/images/symbol.png b/src/bin/mu/mu-interp/images/symbol.png new file mode 100644 index 000000000..6cca7b26c Binary files /dev/null and b/src/bin/mu/mu-interp/images/symbol.png differ diff --git a/src/bin/mu/mu-interp/images/type.png b/src/bin/mu/mu-interp/images/type.png new file mode 100644 index 000000000..cafb6ccca Binary files /dev/null and b/src/bin/mu/mu-interp/images/type.png differ diff --git a/src/bin/mu/mu-interp/main.cpp b/src/bin/mu/mu-interp/main.cpp new file mode 100644 index 000000000..d27310c66 --- /dev/null +++ b/src/bin/mu/mu-interp/main.cpp @@ -0,0 +1,375 @@ +// +// Copyright (c) 2001 Jim Hourihan +// +// SPDX-License-Identifier: Apache-2.0 +// +#include +#include +#ifdef MU_USE_READLINE +#include +#include +#endif +#if 0 +#include +#include +#endif +#include +#include +#include +#define protected public +#include +#undef protected +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LINKED_MODULES +#include +#include +#include +#include +#include +#include +#include +#endif + +#ifndef RUSAGE_SELF +#define RUSAGE_SELF 0 +#endif + + +using namespace std; +using namespace Mu; +typedef Mu::SymbolTable::SymbolHashTable HT; + +#if 0 +struct rusage start_usage, end_usage, garbage_usage; +#endif + +int noReadline = 1; +int why = 0; +int debug = 0; +int compile = 0; + +GenericMachine* machine=0; +MuLangContext* context=0; +MuLangLanguage* language=0; +Process* process=0; +Thread* gThread=0; + +#if 0 +#ifdef MU_USE_BOEHM_COLLECTOR +void * operator new(size_t size) { return GC_MALLOC(size); } +void * operator new[](size_t size) { return GC_MALLOC(size); } +void operator delete(void *pnt) { /*free(pnt);*/ } +void operator delete[](void *pnt) { /*free(pnt);*/ } +#endif +#endif + +#if 0 +//---------------------------------------------------------------------- + +double +seconds(timeval start, timeval end) +{ + return double(end.tv_sec) + double(end.tv_usec)/1000000.0 + - double(start.tv_sec) - double(start.tv_usec)/1000000.0; +} +#endif + +//---------------------------------------------------------------------- + +void +dumpSymbols(Symbol *s, int depth) +{ + for (int i=0; ioutput(cout); + cout << endl; + + if (s->symbolTable()) + { + HT& table = s->symbolTable()->hashTable(); + + for (HT::Iterator it(table); it; ++it) + { + cout << it.index() << ":"; + for (Symbol *ss = (*it); ss; ss = ss->nextOverload()) + { + dumpSymbols(ss,depth+1); + } + } + } +} + +//---------------------------------------------------------------------- + +#ifdef PLATFORM_WINDOWS +extern "C" void ptw32_processInitialize(void); +#endif + +int +main(int argc, char **argv) +{ +//#ifdef PLATFORM_WINDOWS + //ptw32_processInitialize(); +//#endif + + char* inFile = 0; + char* printFunc = 0; + char* streamName = 0; + int print = 0; + int symbols = 0; + int noeval = 0; + int callmain = 0; + int noninteractive = 0; + int usage = 0; + int notruncate = 0; + int disableGC = 0; + + GarbageCollector::init(); + + if (arg_parse + (argc, argv, + "", "Usage: %s [options]", argv[0], + "[%S]", &inFile, "input .mu source file", + "-print", ARG_FLAG(&print), "print parse tree in lispy form", + "-printFunc %S", &printFunc, "print function in lispy form", + "-stdin", ARG_FLAG(&noninteractive), "non interactive mode", + "-no-readline", ARG_FLAG(&noReadline), "don't use readline library", + "-why", ARG_FLAG(&why), "verbose function choice information", + "-debug", ARG_FLAG(&debug), "include debug information", + "-symbols", ARG_FLAG(&symbols), "output root symbol table", + "-compile", ARG_FLAG(&compile), "compile muc files on demand", + "-noeval", ARG_FLAG(&noeval), "don't evaluate", + "-notruncate", ARG_FLAG(¬runcate), "don't truncate long output", + "-no-gc", ARG_FLAG(&disableGC), "turn off garbage collector", + "-usage", ARG_FLAG(&usage), "show usage", + "-name", ARG_FLAG(&streamName), "name to use for error reporting", + "-main", ARG_FLAG(&callmain), "call the main() function if it exists", + NULL) < 0) + { + exit( 0 ); + } + + machine = new GenericMachine(); + context = new MuLangContext("basic","main"); + language = new MuLangLanguage(); + process = new Process(context); + + if (disableGC) GarbageCollector::disable(); + + Module::setCompileOnDemand(compile == 1, compile == 1); + +#ifdef LINKED_MODULES + Module* autodoc = new Mu::AutoDocModule(context, "autodoc"); + Module* io = new Mu::IOModule(context, "io"); + Module* image = new Mu::ImageModule(context, "image"); + Module* sys = new Mu::SystemModule(context, "system"); + Module* enc = new Mu::SystemModule(context, "encoding"); + Module* qt = new Mu::qtModule(context, "qt"); + + context->globalScope()->addSymbol(autodoc); + context->globalScope()->addSymbol(image); + context->globalScope()->addSymbol(io); + context->globalScope()->addSymbol(sys); + context->globalScope()->addSymbol(enc); + context->globalScope()->addSymbol(qt); +#endif + + context->verbose(why ? true : false); + context->debugging(debug ? true : false); + gThread = process->newApplicationThread(); + + if (!noninteractive && !inFile) + { + InteractiveSession session; + session.run(context, process, gThread); + return 0; + } + + Context::PrimaryBit fence(context, true); + + try + { + if (!inFile) + { + context->parseStdin(process, streamName); + } + else + { + context->parseFile(process, inFile); + } + } + catch (Exception& exc) + { + cerr << "ERROR: " << exc.what() << endl; + if (exc.backtrace().size()) + { + cerr << exc.backtraceAsString(); + } + else + { + cerr << "no backtrace available" << endl; + } + exit(-1); + } + catch (std::exception& e) + { + cerr << "ERROR: (std) " << e.what() << endl; + exit(-1); + } + catch (...) + { + cerr << "ERROR: Uncaught exception" << endl; + exit(-1); + } + + if (symbols) + { + cout << "SYMBOLS\n" << flush; + cout << "-------\n" << flush; + dumpSymbols(context->globalScope(),0); + cout << endl << flush; + } + + if (process->rootNode()) + { + if (print) + { + cout << "\nNODE TREE\n" << flush; + NodePrinter printer(process->rootNode(), cout, + NodePrinter::Lispy); + printer.traverse(); + cout << endl; + } + + +#if 0 + getrusage(RUSAGE_SELF, &start_usage); +#endif + + if (!noeval) + { + Value v = process->evaluate(gThread); + + if (const Type *type = gThread->returnValueType()) + { + if (!type->isTypePattern()) + { + //type->output(cout); + cout << "result"; + cout << " => "; + type->outputValue(cout, v, notruncate != 0); + cout << endl << flush; + } + } + else + { + cout << "=> Unknown return type and value" << endl; + } + } + } + + if (printFunc) + { + QualifiedName n = context->internName(printFunc); + + if (const Symbol *sym = + context->globalScope()->findSymbolByQualifiedName(n, false)) + { + for (const Symbol *s = sym->firstOverload(); s; s = s->nextOverload()) + { + if (const Mu::Function *f = dynamic_cast(s)) + { + cout << "Function: " << f->name() << " "; + + if (f->body()) + { + NodePrinter printer(f->body(), cout, + NodePrinter::Lispy); + printer.traverse(); + } + else + { + cout << " is native"; + } + + cout << endl << flush; + } + } + } + } + + if (callmain) + { + if (Name n = context->lookupName("main")) + { + if (Mu::Function *f = + context->globalScope()->findSymbolOfType(n)) + { + StringType *stype = context->globalScope()-> + findSymbolOfType(context->lookupName("string")); + + DynamicArrayType *arrayType = + static_cast(context->arrayType(stype, 1, 0)); + DynamicArray *array = new DynamicArray(arrayType, 1); + + array->resize(argc); + + for (int i=0; i < argc; i++) + { + StringType::String *s = stype->allocate(argv[i]); + array->element(i) = s; + } + + cout << "RUNNING MAIN:\n"; + + Mu::Function::ArgumentVector vargs; + vargs.push_back(Value(array)); + process->call(gThread, f, vargs); + } + } + } + +#if 0 + getrusage(RUSAGE_SELF, &end_usage); + GarbageCollector::collect(); + getrusage(RUSAGE_SELF, &garbage_usage); + + if (usage && (!noeval || callmain)) + { + cout << "EVALUATION USAGE: " + << seconds(start_usage.ru_utime, end_usage.ru_utime) + << " user, " + << seconds(start_usage.ru_stime, end_usage.ru_stime) + << " system.\n"; + + cout << "FINAL GARBAGE USAGE: " + << seconds(end_usage.ru_utime, garbage_usage.ru_utime) + << " user, " + << seconds(end_usage.ru_stime, garbage_usage.ru_stime) + << " system.\n"; + } +#endif + + + return 0; +} + diff --git a/src/bin/mu/mu-interp/mu-interp.wrapper b/src/bin/mu/mu-interp/mu-interp.wrapper new file mode 100755 index 000000000..558f78d84 --- /dev/null +++ b/src/bin/mu/mu-interp/mu-interp.wrapper @@ -0,0 +1,43 @@ +#!/bin/tcsh -f +# +# This script sets the APP_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set APP_HOME if invoked from a link to the script +# and APP_HOME is not already set +# + +set noglob +set name = mu-interp + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +echo RV_HOME = $RV_HOME +set platform = i386 +set bin = "$RV_HOME/bin/$name.bin.$platform" + +if (! (-e $bin) ) then + set bin = "$RV_HOME/bin/$name.bin" +endif + +if (! (-e $bin) ) then + echo "ERROR: binary " $bin " not found" + exit(-1) +endif + +unsetenv BUILD_ROOT +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec binary + +exec $bin $*:q diff --git a/src/bin/mu/mu-interp/test/Y.mu b/src/bin/mu/mu-interp/test/Y.mu new file mode 100644 index 000000000..8eecadf80 --- /dev/null +++ b/src/bin/mu/mu-interp/test/Y.mu @@ -0,0 +1,17 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// The Y combinator in Mu +// NOTE: this isn't really possible without a purely dynamic type. +// The simply typed lambda calculus cannot express Y() +// + +//\: Y (le) +//{ +// (\: (f) {f(f);}) (\: (f) { le( \: (x) { f(f)(x); }); }); +//} + diff --git a/src/bin/mu/mu-interp/test/abs.mu b/src/bin/mu/mu-interp/test/abs.mu new file mode 100644 index 000000000..116ad9e9e --- /dev/null +++ b/src/bin/mu/mu-interp/test/abs.mu @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +2+math.abs(-10) diff --git a/src/bin/mu/mu-interp/test/abstract_interface.mu b/src/bin/mu/mu-interp/test/abstract_interface.mu new file mode 100644 index 000000000..dcdd8f0de --- /dev/null +++ b/src/bin/mu/mu-interp/test/abstract_interface.mu @@ -0,0 +1,34 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +interface: Sized +{ + function: size (int; Sized); + function: bar (void; Sized); +} + +try +{ + // + // This dynamic cast should throw. int[] does not properly + // implement the Sized interface because function bar() is + // abstract. + // + + // + // NOTE: if you make the int[] a constant (replace i with 1 + // for example). It will throw at parse time not at runtime. + // hence the use of "i" to force it at runtime. + // + + int i = 1; + Sized foo = int[] {i, 2, 3}; + assert(false); +} +catch (exception exc) +{ + print("ok\n"); +} diff --git a/src/bin/mu/mu-interp/test/ack.cpp b/src/bin/mu/mu-interp/test/ack.cpp new file mode 100644 index 000000000..3c08eb58b --- /dev/null +++ b/src/bin/mu/mu-interp/test/ack.cpp @@ -0,0 +1,17 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include +using namespace std; + +int ack (int m, int n) +{ + return m == 0 ? (n+1) : (n == 0 ? ack(m-1, 1) : ack(m-1, ack(m, n-1))); +} + +int main(int argc, const char** argv) +{ + cout << ack(atoi(argv[1]), atoi(argv[2])) << endl; +} diff --git a/src/bin/mu/mu-interp/test/ack.mu b/src/bin/mu/mu-interp/test/ack.mu new file mode 100644 index 000000000..164ca060b --- /dev/null +++ b/src/bin/mu/mu-interp/test/ack.mu @@ -0,0 +1,52 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// from http://inferno.bell-labs.com/cm/cs/who/bwk/interps/pap.html +// +// The next program evaluates Ackermann's function. Computing ack(3,k) = +// 2^(k+3)-3 requires at least 4^(k+1) function calls, and reaches a +// recursive depth of 2^(k+3)-1, so this test gives the function call +// mechanism a thorough workout. +// +// This will fail for a large enough k that the C stack is exhausted. So if +// its not run in the main thread it will fail when the pthread stack limit +// is hit. +// +// The equivalent python program will throw at k == 7 because of recursion +// limits. The equivalent perl program is approx the same speed as python, +// but does not die. The original paper tested up to k == 8. +// + +function: ack (int; int m, int n) +{ + if m == 0 then (n+1) else (if n == 0 then ack(m-1, 1) else ack(m-1, ack(m, n-1))); +} + +function: ack_long (int; int m, int n) +{ + if (m == 0) + { + return n + 1; + } + else + { + if (n == 0) + { + return ack_long(m-1, 1); + } + else + { + return ack_long(m-1, ack_long(m, n-1)); + } + } +} + + +int k = 8; +//assert(ack(3,k) == math.pow(2, (k+3)) - 3); +//assert(ack_long(3,k) == ack(3,k)); +ack(3,k); + diff --git a/src/bin/mu/mu-interp/test/ack.pl b/src/bin/mu/mu-interp/test/ack.pl new file mode 100644 index 000000000..5a5b4662c --- /dev/null +++ b/src/bin/mu/mu-interp/test/ack.pl @@ -0,0 +1,32 @@ +#!/usr/bin/env perl +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# +# Perl seems to handle this pretty well +# + +sub ack +{ + $m = shift( @_ ); + $n = shift( @_ ); + if( $m == 0 ) + { + return $n + 1; + } + elsif( $n == 0 ) + { + return ack($m-1, 1); + } + else + { + return ack($m-1, ack($m, $n-1)); + } +} + +$k = 10; +ack( 3, $k ); + diff --git a/src/bin/mu/mu-interp/test/ack.py b/src/bin/mu/mu-interp/test/ack.py new file mode 100644 index 000000000..6341bf897 --- /dev/null +++ b/src/bin/mu/mu-interp/test/ack.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +import sys + +sys.setrecursionlimit(10000) + +# +# Unfortunately, python will throw if k > 6 because its maximum recursion +# depth is met. +# + + +def ack(m, n): + if m == 0: + return n + 1 + elif n == 0: + return ack(m - 1, 1) + else: + return ack(m - 1, ack(m, n - 1)) + + +k = 8 +print(ack(3, k)) diff --git a/src/bin/mu/mu-interp/test/alias.mu b/src/bin/mu/mu-interp/test/alias.mu new file mode 100644 index 000000000..afc4760ea --- /dev/null +++ b/src/bin/mu/mu-interp/test/alias.mu @@ -0,0 +1,48 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +float_array := float[]; +float_array foo; +foo.push_back(0); +foo.push_back(1); +foo.push_back(2); +foo.push_back(3); + +a := float; // a = float +b := a[]; // b = float[] +c := b[]; // c = float[][] +d := c; // d = float[][] +e := b; // e = float[] + +d array; +array.push_back( e() ); +array.back().push_back(69.0); + +m := math; // module alias +sine := m.sin; // function alias + +assert(sine(array.back().back()) == math.sin(69.0)); + +potato := float_array.push_back; // member function alias (exact) +assert(foo.size() == 4); + +potato(foo, 123.321); // aliased call to a member function + +assert(foo.size() == 5); +assert(foo[4] == 123.321); + +int crap = 10; +wad := crap; // wad is an alias for crap +assert(wad == 10); +wad = 20; +assert(crap == 20); + +function: add (float; float x, float y) { x + y; } + +sum := add; +assert( sum(1.0, 2.0) == 3.0 ); + +vec := vector float[4]; + diff --git a/src/bin/mu/mu-interp/test/array.mu b/src/bin/mu/mu-interp/test/array.mu new file mode 100644 index 000000000..944c42a46 --- /dev/null +++ b/src/bin/mu/mu-interp/test/array.mu @@ -0,0 +1,154 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +use math; +require runtime; + +float[] array; + +print(array); +print("\n"); +array.push_back(1.0); +array.push_back(2.0); +assert(array.size() == 2); +assert(array[0] == 1.0 && array[1] == 2.0); + + +for (int i=0; i < 1000; i++) array.push_back(i); +assert(array.size() == 1002); + +string[] stringArray; +for (int i=0; i < 10000; i++) +{ + stringArray.push_back("hello " + i); +} + +print("starting copy\n"); +string[] copyOfStringArray = string[](stringArray); +print("finished copy\n"); + +for (int i=0; i < stringArray.size(); i += 1000) +{ + print(stringArray[i] + "\n"); +} + +float[][] marray; +float[] a; +a.push_back(1); +marray.push_back( a ); +marray.push_back( a ); + +(vector float[4])[] varray; +varray.push_back(vec4f(0,0,0,0)); + +varray.front() = vec4f(1,2,3,4); + +for (int i=0; i < 1000; i++) +{ + varray.push_back( vec4f(i,i,i,i) ); +} + +varray.back() = vec4f(4,3,2,1); + +for (int i=0; i < varray.size(); i += 100) +{ + print(varray[i] + "\n"); +} + +for (int i=varray.size(); i > 0; i--) +{ + vec4f v = varray.pop_back(); +} + + +float[1] qq = float[1](); +qq[0] = 99.0; +print(qq[0] + "\n"); + +// this one is not using the declaration syntax -- its using the +// aggregate_value syntax +float[2][] ca; +ca = (float[2][] { {1, 2}, {3, 4}, {1, 2}, }); +assert(ca.size() == 3); +assert(ca[0][1] == 2.0); +assert(ca[1][0] == 3.0); + +// using declaration aggregate_initializer +float[4,4] R = {1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1}; + + +vec4f[4] axis = { {0, 0, 0, 1}, + {1, 0, 0, 1}, + {0, 1, 0, 1}, + {0, 0, 1, 1} }; + +for (int i=0; i < axis.size(); i++) +{ + print("axis[" + i + "] = " + axis[i] + "\n"); +} + +string[][] names = { + { "foo", "bar", "baz", "bing", "bang", "bong", "caaaa" }, + { "and", "eat", "this" }, + {"yo", "yo", "yo"} }; + +int[] nums = { math.pi, math.e, 10 + 20, sin(1.23), names.size() }; +assert(nums.back() == names.size()); + + +float[2,2][][2][] jesush = +{ + { + { {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5}, }, + { {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5} }, + }, + + { + { {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5}, }, + { {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5} }, + }, + + { + { {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5}, }, + { {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5}, {1, 2, 4, 5} }, + }, +}; + + +function: takes_an_array(void; int[] array) +{ + for (int i=0; i < array.size(); i++) + { + print(array[i] + "\n"); + } +} + +takes_an_array( int[] {1, 2, 3, 4} ); + + +module: foo +{ + class: test { int x; } +} + +\: x () +{ + let x = foo.test[2](foo.test(1), foo.test(2)), + y = foo.test[2](); + let a = foo.test[](), + b = foo.test[](); + + x == y; + a == b; + a eq b; + x eq y; +} + + +x(); diff --git a/src/bin/mu/mu-interp/test/array_big.mu b/src/bin/mu/mu-interp/test/array_big.mu new file mode 100644 index 000000000..c6285be2a --- /dev/null +++ b/src/bin/mu/mu-interp/test/array_big.mu @@ -0,0 +1,4012 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +math.vec3f[] points = { +{0.773228, -0.0707359, 0.63017}, +{0.416156, 0.745204, -0.521042}, +{-0.576685, 0.51096, -0.63746}, +{0.628077, -0.0545077, 0.77624}, +{-0.207712, 0.819163, -0.534629}, +{-0.712806, 0.565174, 0.415313}, +{-0.458428, 0.468577, -0.755168}, +{0.889964, -0.316396, -0.328418}, +{-0.627641, 0.455314, -0.631472}, +{-0.584438, 0.547328, 0.599053}, +{0.712047, -0.700045, 0.0540959}, +{-0.011151, 0.963789, 0.266432}, +{-0.856277, 0.347952, -0.381732}, +{-0.144926, 0.867855, -0.475209}, +{0.00939158, -0.988231, -0.152679}, +{-0.136484, -0.859115, -0.493248}, +{-0.91387, 0.405732, -0.0149339}, +{0.755954, -0.564009, 0.332306}, +{0.134026, -0.96587, 0.22166}, +{-0.771265, -0.485879, 0.411185}, +{0.0295565, 0.995518, 0.0898323}, +{0.393383, -0.835427, 0.383812}, +{0.796791, -0.601556, 0.0570406}, +{0.906888, -0.305162, -0.290568}, +{0.617255, 0.647751, 0.44656}, +{0.145027, 0.230601, 0.96218}, +{0.403916, -0.445564, -0.798953}, +{-0.325151, 0.921561, 0.212139}, +{-0.739689, -0.487003, 0.464422}, +{0.951377, -0.289948, -0.10398}, +{-0.42954, 0.327362, -0.841623}, +{0.309021, -0.368969, 0.876566}, +{0.410972, 0.0270727, 0.911246}, +{-0.45521, 0.76524, 0.455184}, +{0.468237, 0.249501, 0.847646}, +{-0.112315, 0.609811, 0.784548}, +{0.936858, 0.145701, -0.317912}, +{-0.132263, 0.517646, -0.84531}, +{0.938413, 0.306321, -0.159841}, +{0.735148, -0.605443, 0.304952}, +{-0.277552, -0.247351, -0.928322}, +{-0.305883, -0.94938, -0.0715083}, +{0.110194, -0.439405, -0.891505}, +{-0.764829, -0.157707, 0.624632}, +{0.743616, 0.0244738, -0.668159}, +{-0.100227, 0.994424, 0.0327802}, +{0.738528, -0.523892, -0.424397}, +{-0.39648, -0.277122, -0.875218}, +{-0.358714, 0.618606, -0.699036}, +{0.595104, -0.801108, -0.0638533}, +{-0.56224, 0.0793175, -0.823162}, +{-0.400038, -0.544725, 0.737051}, +{-0.902739, -0.340511, 0.262897}, +{0.135766, 0.291534, -0.946877}, +{-0.298857, 0.871436, 0.388953}, +{0.72502, -0.549745, -0.41488}, +{0.741171, 0.576167, 0.344525}, +{0.299818, 0.706882, -0.640646}, +{-0.190345, 0.852489, -0.486859}, +{0.687994, -0.719283, 0.0964165}, +{-0.211431, -0.114313, -0.970685}, +{0.475486, -0.484484, 0.734294}, +{-0.945383, -0.196005, -0.260446}, +{-0.199893, -0.146118, 0.968861}, +{-0.116254, 0.522693, 0.844557}, +{0.328029, 0.934942, 0.135202}, +{-0.549693, 0.795764, 0.254161}, +{-0.691615, -0.537071, -0.482932}, +{-0.425136, 0.120736, 0.897041}, +{0.720801, -0.11663, 0.68326}, +{-0.760693, -0.537024, -0.364625}, +{-0.490681, -0.521062, -0.698374}, +{-0.635408, -0.200028, 0.745819}, +{-0.462631, -0.215397, -0.859986}, +{0.088217, 0.522754, -0.847907}, +{-0.941341, 0.268944, -0.203829}, +{-0.696294, 0.652568, 0.298881}, +{-0.263004, 0.76037, -0.593857}, +{-0.0372477, -0.116435, 0.9925}, +{-0.262251, 0.483955, -0.834872}, +{0.559407, -0.735327, -0.382567}, +{-0.91208, -0.106689, 0.395889}, +{0.269606, 0.811593, 0.518295}, +{-0.85237, -0.298187, 0.429594}, +{0.646107, 0.747663, -0.153444}, +{0.374832, 0.646946, 0.66405}, +{-0.397651, 0.566523, 0.721751}, +{0.447445, 0.0366508, -0.89356}, +{-0.0851264, -0.587765, -0.804541}, +{-0.872947, 0.220017, -0.43538}, +{-0.198704, -0.344756, 0.91742}, +{-0.0705505, 0.206695, 0.975858}, +{-0.278725, -0.18281, -0.942811}, +{0.853215, 0.361167, 0.376274}, +{0.723529, -0.158771, 0.671787}, +{-0.0692671, -0.0403614, -0.996781}, +{-0.369508, -0.896766, -0.243463}, +{0.460927, 0.875264, 0.146489}, +{0.898307, -0.43282, -0.0755708}, +{-0.752539, -0.0288221, 0.657917}, +{-0.963828, 0.162074, 0.211584}, +{-0.895963, 0.204815, -0.394083}, +{-0.751503, -0.603236, -0.267112}, +{-0.292005, -0.881827, -0.370289}, +{-0.731042, 0.584741, -0.351648}, +{-0.569985, -0.606159, -0.554696}, +{0.984065, 0.135082, 0.115625}, +{-0.409119, 0.145142, -0.900864}, +{0.0468043, -0.117721, -0.991943}, +{0.835866, 0.548802, 0.0119943}, +{-0.296009, -0.847248, 0.441078}, +{0.456364, 0.647987, 0.609791}, +{-0.879321, -0.381379, 0.285211}, +{0.438641, 0.665255, 0.604177}, +{0.233721, -0.350261, 0.907024}, +{0.740642, -0.670459, 0.0439889}, +{-0.0568553, 0.821598, 0.567226}, +{0.728787, -0.464168, -0.503405}, +{-0.697234, -0.656311, -0.288307}, +{-0.562232, 0.790107, -0.244185}, +{-0.398445, -0.838486, -0.37173}, +{0.433112, 0.846443, -0.309755}, +{0.0608475, -0.860467, 0.50586}, +{-0.610856, 0.786375, -0.0920245}, +{-0.779644, 0.525653, -0.340358}, +{0.812107, 0.0300884, 0.582732}, +{-0.182346, 0.965103, 0.187952}, +{0.911246, 0.33048, 0.245793}, +{0.267164, -0.388335, -0.881941}, +{0.187956, 0.884571, 0.426857}, +{-0.314674, 0.931449, -0.182711}, +{-0.307942, -0.588211, 0.747783}, +{-0.107161, 0.580741, -0.807004}, +{0.232607, 0.106493, 0.966723}, +{-0.547762, -0.625059, 0.55611}, +{-0.691053, -0.438266, 0.574776}, +{-0.813995, -0.578978, 0.0468682}, +{-0.112682, -0.644158, -0.756547}, +{0.0528935, -0.841249, -0.538054}, +{0.375052, -0.682218, 0.627626}, +{-0.989778, -0.00415305, -0.14256}, +{-0.330601, -0.715029, 0.615984}, +{0.81196, 0.157358, 0.562103}, +{0.223804, 0.932213, -0.284415}, +{0.759406, -0.64658, -0.0723655}, +{0.456811, -0.0109105, 0.889497}, +{-0.564292, -0.641908, -0.519161}, +{-0.544406, -0.300056, 0.783319}, +{0.0811832, -0.0327508, 0.996161}, +{0.279577, 0.378714, -0.882277}, +{0.250052, 0.630417, 0.73488}, +{-0.700104, 0.633747, -0.328969}, +{0.0465434, 0.862122, -0.504558}, +{-0.479891, 0.856301, 0.190924}, +{-0.960344, 0.00736862, 0.278719}, +{0.687248, 0.469702, 0.554138}, +{-0.307253, 0.0928629, 0.947086}, +{-0.877638, -0.223389, -0.424086}, +{0.268897, 0.626969, -0.731166}, +{0.0768966, 0.607158, -0.790852}, +{0.844343, -0.470578, -0.256205}, +{-0.821025, -0.530532, 0.210841}, +{0.118125, -0.43139, 0.894399}, +{0.514356, -0.725651, 0.457021}, +{0.356702, -0.49472, 0.792474}, +{-0.943802, 0.328363, 0.0376221}, +{0.523588, -0.706405, -0.476285}, +{0.22104, 0.96149, 0.163336}, +{-0.636745, -0.129741, -0.760081}, +{0.476682, 0.308092, 0.823319}, +{0.807517, 0.499063, 0.314409}, +{0.531268, 0.20802, 0.821269}, +{-0.856158, -0.330728, -0.397004}, +{0.969985, -0.115444, 0.214012}, +{0.355389, 0.054517, 0.933127}, +{-0.84136, 0.369781, -0.394176}, +{0.628313, -0.699033, 0.341431}, +{0.0869167, -0.544611, 0.834173}, +{0.715635, 0.204073, -0.667997}, +{0.549517, -0.456195, -0.699941}, +{-0.0865944, 0.180061, -0.979836}, +{0.995059, 0.0984135, -0.0131148}, +{-0.873228, -0.459479, 0.162334}, +{-0.463993, 0.0894609, -0.88131}, +{0.766488, 0.482736, 0.42363}, +{0.392496, 0.907114, -0.151958}, +{-0.298302, -0.637138, 0.710683}, +{0.337122, -0.191893, -0.921697}, +{-0.301024, 0.278723, 0.911975}, +{-0.952967, -0.272177, 0.133316}, +{-0.222683, -0.812622, -0.538571}, +{0.327837, 0.540293, 0.774988}, +{0.979412, 0.0957793, 0.177705}, +{0.698319, -0.577057, -0.423504}, +{0.165388, 0.563774, -0.8092}, +{0.353977, -0.923373, 0.148602}, +{-0.398198, -0.344001, 0.850354}, +{0.848922, 0.522413, -0.0800965}, +{-0.406472, -0.893471, 0.191024}, +{0.906404, 0.414709, -0.0803057}, +{-0.845286, 0.0774964, 0.528663}, +{0.0840234, 0.537443, 0.839104}, +{0.239902, -0.8678, -0.435166}, +{-0.236993, -0.697165, 0.676606}, +{-0.293545, -0.369797, 0.881522}, +{0.630149, 0.383197, 0.675331}, +{0.510814, -0.837994, 0.191927}, +{-0.378447, 0.925, -0.0339665}, +{-0.786209, 0.278189, -0.551803}, +{0.145857, -0.781952, -0.606034}, +{0.857178, -0.163386, 0.488417}, +{-0.661816, 0.198877, 0.722805}, +{-0.54541, -0.724986, -0.420621}, +{0.894865, -0.227065, 0.384264}, +{0.424952, -0.90038, -0.0934404}, +{-0.379815, -0.601471, 0.702833}, +{0.908643, -0.38995, -0.149354}, +{-0.204291, 0.411466, -0.888235}, +{0.0818337, -0.983081, -0.163877}, +{-0.856062, -0.506671, -0.102187}, +{0.996917, -0.0635778, 0.0459764}, +{0.308308, -0.587913, 0.747867}, +{0.497123, -0.808183, 0.315767}, +{0.669899, 0.601062, 0.435844}, +{-0.940703, -0.00564236, 0.339185}, +{-0.242015, 0.151756, 0.958331}, +{0.506813, 0.815821, -0.278526}, +{0.444599, 0.760716, -0.47291}, +{0.310941, -0.935585, -0.167323}, +{-0.840326, 0.537417, 0.0709556}, +{0.291353, -0.00415028, 0.956607}, +{0.283437, -0.827602, -0.484499}, +{-0.775543, -0.624146, -0.0947355}, +{0.690953, -0.670945, -0.269102}, +{0.643273, -0.762381, 0.0705382}, +{0.0395429, -0.928014, 0.37044}, +{-0.996862, 0.0167314, -0.0773699}, +{0.163089, -0.939767, 0.300399}, +{-0.256541, -0.201666, -0.945261}, +{-0.492165, 0.622103, -0.608902}, +{0.23486, -0.561388, -0.793526}, +{-0.89888, -0.415453, -0.139334}, +{-0.0952387, 0.0558736, 0.993885}, +{0.468325, -0.75266, -0.46279}, +{0.420054, -0.233894, -0.87684}, +{0.889733, 0.387452, -0.241363}, +{-0.575333, -0.766234, 0.286142}, +{-0.868292, -0.426643, 0.253072}, +{-0.646458, 0.686084, -0.333737}, +{0.00856148, 0.303768, -0.952708}, +{-0.723183, -0.690376, -0.0196847}, +{-0.116966, 0.912956, 0.390936}, +{-0.627502, -0.0296827, 0.778049}, +{0.160989, -0.925888, -0.341781}, +{0.399747, 0.535066, 0.744249}, +{-0.546954, 0.835973, -0.0446094}, +{0.846422, 0.250391, -0.469973}, +{-0.656874, 0.741554, -0.136433}, +{-0.519668, 0.302656, 0.798965}, +{0.983444, 0.132693, -0.123412}, +{-0.106091, -0.991249, -0.0785494}, +{-0.301078, -0.852039, 0.428232}, +{0.86812, -0.112627, -0.483407}, +{-0.351069, -0.289318, 0.890531}, +{0.675497, -0.144483, 0.723069}, +{0.867892, -0.471571, 0.156153}, +{-0.681761, 0.0779361, 0.727412}, +{-0.742264, 0.454137, -0.492751}, +{0.775695, 0.592746, -0.216679}, +{-0.109107, 0.174172, 0.978652}, +{0.947582, 0.170928, -0.269949}, +{-0.185424, 0.636468, -0.748683}, +{0.366333, 0.46219, -0.807577}, +{-0.108753, 0.844424, 0.52452}, +{0.531399, -0.843963, -0.0730813}, +{0.127826, -0.991389, -0.0284202}, +{-0.791628, -0.542226, 0.281629}, +{0.217244, 0.84079, -0.49586}, +{-0.11878, 0.991226, -0.0579812}, +{-0.802878, -0.507145, -0.313354}, +{-0.500109, -0.843081, 0.197749}, +{0.158633, -0.222318, 0.961982}, +{0.0220684, 0.253949, 0.966966}, +{-0.314337, 0.686753, 0.65541}, +{-0.302056, -0.340231, -0.890508}, +{0.891865, -0.443889, 0.0868347}, +{-0.466258, 0.318577, -0.825295}, +{-0.530974, -0.846639, -0.0356056}, +{-0.486421, 0.362926, 0.794783}, +{0.919445, 0.33796, -0.201009}, +{0.775145, 0.392438, -0.495118}, +{0.300117, -0.460474, -0.8354}, +{-0.993074, 0.114075, -0.0281272}, +{0.258174, 0.928974, -0.26524}, +{0.907233, -0.41835, -0.0437305}, +{-0.738859, 0.238482, -0.63025}, +{-0.918257, 0.314584, -0.240502}, +{0.588211, -0.136498, -0.797105}, +{0.186011, 0.643534, 0.742471}, +{0.121775, -0.0281642, -0.992158}, +{0.526695, -0.181093, -0.830541}, +{-0.950147, -0.183778, 0.251884}, +{0.926374, -0.286535, 0.244396}, +{-0.783032, -0.614788, 0.0943231}, +{0.468353, -0.835417, 0.287617}, +{0.637894, 0.0836099, -0.765572}, +{-0.987965, 0.0797934, 0.132505}, +{-0.759042, -0.0957109, -0.643968}, +{-0.292905, 0.764565, 0.574149}, +{0.437598, 0.133189, 0.889252}, +{-0.803098, -0.111377, -0.585344}, +{-0.461463, 0.770282, -0.440134}, +{0.668501, -0.597496, 0.442837}, +{0.228321, 0.146993, -0.962425}, +{0.919585, -0.391379, 0.0344453}, +{-0.311667, -0.927296, -0.207331}, +{-0.644564, -0.589707, -0.486604}, +{-0.201312, -0.96964, -0.13882}, +{0.0818045, 0.767619, 0.635665}, +{-0.85612, 0.00495211, -0.516754}, +{-0.340929, -0.84392, -0.414206}, +{0.637635, -0.428655, -0.64006}, +{0.776018, 0.0691716, 0.626907}, +{0.139244, 0.0653424, -0.9881}, +{-0.828921, 0.505302, 0.239915}, +{0.095148, -0.604472, 0.790924}, +{0.475247, -0.800319, -0.365555}, +{-0.306444, 0.947083, 0.0955311}, +{-0.0115119, 0.0940581, 0.9955}, +{-0.0351699, 0.220168, -0.974828}, +{-0.569081, 0.69178, 0.444507}, +{0.613257, -0.719014, 0.327008}, +{-0.840663, -0.0987814, -0.532473}, +{0.714746, -0.198586, 0.670597}, +{0.185513, 0.214041, -0.959047}, +{0.646273, -0.754485, -0.114387}, +{0.0872603, -0.950902, -0.296936}, +{0.618238, -0.579992, -0.530463}, +{-0.226296, 0.880014, 0.417571}, +{0.604464, -0.0269044, 0.796178}, +{-0.0111121, -0.210355, -0.977562}, +{0.362683, -0.808882, -0.462785}, +{0.908004, -0.370596, -0.195413}, +{-0.455336, -0.872387, -0.177791}, +{0.421336, 0.615252, -0.666289}, +{0.700132, -0.522946, 0.486152}, +{-0.739493, -0.590829, 0.322601}, +{-0.711318, -0.187868, -0.677298}, +{-0.374682, 0.847002, 0.377095}, +{0.341554, 0.568445, -0.748473}, +{-0.490356, 0.737937, 0.463681}, +{0.490427, 0.451831, 0.745205}, +{0.577985, -0.273918, -0.768702}, +{0.48346, -0.3793, -0.788922}, +{0.638055, 0.474414, 0.606479}, +{-0.216246, 0.913922, -0.343488}, +{-0.612006, -0.7625, -0.209865}, +{-0.0462658, -0.888707, 0.456136}, +{0.531916, -0.517578, -0.670207}, +{-0.124269, -0.181183, 0.975566}, +{0.968812, 0.247608, 0.0096367}, +{-0.466169, 0.212727, 0.85874}, +{-0.399143, -0.776152, 0.488132}, +{0.449739, -0.726393, 0.5197}, +{0.372397, 0.650509, -0.661935}, +{0.247224, 0.28173, -0.927097}, +{-0.0269669, -0.425197, -0.904699}, +{-0.592281, -0.624566, -0.509039}, +{0.223519, 0.969609, 0.099483}, +{-0.685228, -0.312143, -0.65805}, +{-0.979773, -0.0630721, -0.189912}, +{-0.312331, 0.50967, -0.801677}, +{-0.772604, -0.42402, -0.472535}, +{-0.0865618, -0.628242, 0.773187}, +{0.628366, -0.547202, -0.552925}, +{-0.95258, -0.304165, -0.00862861}, +{0.185436, -0.980757, -0.0610624}, +{0.256248, -0.781323, -0.569097}, +{0.672993, 0.716756, -0.182595}, +{-0.263249, -0.878413, 0.398862}, +{0.236007, 0.384086, -0.892625}, +{-0.354199, 0.86761, -0.348993}, +{-0.968795, -0.155169, 0.193283}, +{0.607872, -0.757559, -0.237897}, +{-0.00713383, 0.991308, 0.131371}, +{0.701572, -0.200974, -0.683672}, +{0.196454, -0.123137, -0.97275}, +{-0.362283, 0.920234, 0.148054}, +{0.980146, 0.140626, 0.139781}, +{-0.236169, -0.26534, -0.934783}, +{0.724327, 0.663034, 0.189039}, +{0.103977, 0.235629, -0.966265}, +{0.438479, -0.549893, -0.710882}, +{0.294696, 0.585785, -0.75499}, +{-0.222673, 0.545372, -0.808075}, +{-0.0329392, 0.697707, 0.715626}, +{0.823651, -0.441359, 0.356092}, +{0.878058, 0.340673, -0.336089}, +{0.737195, -0.414679, -0.533466}, +{0.343596, 0.403489, 0.84802}, +{0.301732, -0.953392, -0.00134519}, +{-0.579529, 0.0248752, 0.814572}, +{-0.311992, 0.203486, -0.928038}, +{-0.260622, -0.929731, -0.260147}, +{-0.488032, -0.872512, 0.0234217}, +{0.670019, -0.674115, 0.310876}, +{-0.492451, -0.724536, 0.482224}, +{0.272679, 0.442394, -0.854362}, +{0.50102, -0.523078, -0.689469}, +{-0.161663, -0.783272, -0.600291}, +{0.463239, 0.230664, -0.855689}, +{0.940174, 0.201025, 0.275067}, +{0.350531, -0.917765, 0.186642}, +{-0.0998532, 0.577415, 0.810322}, +{-0.0863993, 0.186613, 0.978627}, +{-0.166405, 0.294152, -0.941161}, +{-0.583173, -0.549524, -0.598275}, +{0.398755, 0.149651, 0.904764}, +{-0.740899, -0.667812, 0.0713804}, +{0.789223, 0.291558, 0.540482}, +{0.556241, 0.333462, -0.761183}, +{-0.157284, -0.182877, -0.970473}, +{0.856007, 0.292164, -0.426488}, +{0.861275, 0.294925, 0.413793}, +{-0.130793, 0.985458, 0.10847}, +{-0.762373, -0.545066, 0.348841}, +{0.84, 0.396466, 0.370424}, +{-0.266077, 0.363858, 0.892642}, +{-0.167493, 0.645786, 0.74492}, +{-0.605832, 0.766507, 0.213155}, +{0.414851, 0.831477, 0.36952}, +{0.21232, 0.964617, -0.156312}, +{-0.46792, 0.875638, 0.119621}, +{-0.751071, 0.178672, 0.635585}, +{-0.650807, -0.739142, -0.173549}, +{-0.0354957, -0.992348, 0.118261}, +{0.476122, 0.561257, -0.676977}, +{-0.106678, -0.992948, 0.0517096}, +{-0.284602, 0.868744, -0.405322}, +{0.1357, -0.564783, -0.814006}, +{-0.788524, 0.476375, -0.388968}, +{-0.289847, 0.0999372, -0.951841}, +{0.958147, 0.0428704, 0.283047}, +{0.367085, 0.249387, -0.896133}, +{0.685667, -0.727446, -0.0261389}, +{-0.842658, 0.123725, -0.524042}, +{-0.952321, -0.0492754, 0.301092}, +{-0.0745599, 0.502934, -0.861103}, +{0.61357, 0.715498, -0.334058}, +{0.0374117, -0.0693219, 0.996893}, +{0.971757, -0.0646142, -0.226964}, +{0.129452, -0.885515, -0.446212}, +{-0.412986, -0.283162, -0.865599}, +{0.950625, 0.219012, -0.219877}, +{0.000377241, -0.37777, 0.9259}, +{-0.0583186, 0.698077, -0.713644}, +{0.987127, -0.0812919, 0.137738}, +{-0.170983, -0.915225, 0.364867}, +{-0.874839, 0.446731, 0.187317}, +{-0.22926, 0.0842784, 0.96971}, +{-0.633999, 0.592477, -0.497007}, +{0.221638, 0.337431, 0.914886}, +{-0.560613, -0.111787, 0.820498}, +{-0.445934, 0.479402, -0.755855}, +{-0.993226, -0.0930046, 0.0696522}, +{0.194687, 0.512055, 0.836598}, +{-0.184829, 0.639398, 0.74633}, +{-0.802382, 0.382521, -0.458106}, +{-0.522831, 0.678685, 0.515786}, +{-0.792363, -0.361871, 0.491132}, +{-0.196644, 0.360822, 0.911668}, +{0.94788, 0.234396, -0.215829}, +{0.229017, 0.929739, 0.288335}, +{0.884601, -0.339868, 0.319328}, +{-0.908981, 0.0302261, 0.415741}, +{-0.402534, 0.810525, 0.425458}, +{-0.509417, 0.859249, 0.0467497}, +{-0.412527, -0.362976, -0.835506}, +{0.699643, -0.68625, 0.1989}, +{-0.124346, -0.871782, -0.473851}, +{0.260759, -0.96334, 0.0630921}, +{-0.189283, 0.167385, -0.967551}, +{0.818593, 0.458835, -0.345508}, +{-0.689358, 0.119099, -0.714563}, +{-0.618479, 0.380763, 0.687389}, +{-0.542576, 0.67917, 0.494308}, +{0.102588, 0.989229, 0.10441}, +{-0.55921, -0.735884, 0.381784}, +{-0.474345, -0.872634, 0.116215}, +{0.976822, -0.201082, -0.0733825}, +{0.863344, 0.259734, 0.432638}, +{-0.221982, -0.107931, -0.969059}, +{-0.0926029, 0.754243, -0.650033}, +{-0.0013734, -0.465455, -0.885071}, +{0.880387, 0.472527, -0.0404635}, +{-0.103507, 0.54038, 0.83503}, +{-0.522344, -0.741191, 0.421655}, +{-0.250401, -0.481238, -0.840065}, +{0.874307, -0.42232, -0.239235}, +{-0.365752, -0.90852, -0.202031}, +{0.164677, 0.797031, 0.581054}, +{0.857968, 0.468565, 0.210567}, +{0.447747, 0.280304, -0.849089}, +{0.411891, 0.466384, -0.782835}, +{-0.0955243, 0.910918, 0.401376}, +{0.112742, 0.974895, -0.192011}, +{-0.0619714, 0.986578, 0.151077}, +{0.110341, 0.876868, -0.467897}, +{0.917522, 0.252298, -0.307406}, +{-0.0363539, 0.964961, -0.259863}, +{-0.117382, 0.951355, -0.284858}, +{0.696467, -0.358928, 0.621373}, +{-0.130061, -0.932408, -0.337192}, +{-0.758023, 0.531265, -0.378363}, +{0.684916, -0.435688, -0.584009}, +{0.589049, -0.568839, 0.573972}, +{-0.0994089, 0.45859, -0.88307}, +{-0.306201, -0.777194, -0.549736}, +{0.933831, 0.356059, 0.0343737}, +{0.99504, 0.0651517, 0.075175}, +{0.889513, -0.208926, 0.406345}, +{0.420731, 0.444207, 0.79099}, +{0.0738016, 0.971211, -0.226499}, +{0.589156, -0.412732, 0.694657}, +{-0.889502, 0.348919, 0.295027}, +{-0.0244612, 0.525746, 0.85029}, +{-0.276309, 0.812251, 0.513713}, +{0.520828, 0.532287, -0.66739}, +{-0.440628, 0.227211, -0.86846}, +{0.72494, 0.161926, -0.669509}, +{-0.889597, 0.355062, -0.287313}, +{0.637384, -0.474001, -0.607507}, +{0.929749, -0.0573182, -0.363704}, +{0.315388, 0.849149, -0.423647}, +{-0.00928232, 0.775113, -0.631754}, +{0.351872, 0.893108, -0.280259}, +{0.382798, 0.741013, 0.551693}, +{-0.867357, 0.233499, 0.439511}, +{0.16179, -0.872313, -0.461404}, +{0.371433, 0.694844, 0.615816}, +{0.275648, 0.0562907, 0.959609}, +{-0.30377, 0.58926, -0.748663}, +{0.107535, 0.0326808, -0.993664}, +{0.928925, -0.365076, 0.0617895}, +{-0.906819, 0.182671, 0.379883}, +{-0.816987, 0.476979, -0.324072}, +{-0.948634, -0.228325, 0.218999}, +{-0.306723, 0.747404, 0.589329}, +{0.523748, -0.601268, -0.603461}, +{-0.367995, -0.0464385, 0.928667}, +{-0.385434, -0.108926, -0.916284}, +{0.626324, 0.301384, -0.718948}, +{-0.929887, -0.292036, 0.223663}, +{-0.911464, 0.0343044, -0.409947}, +{0.655025, 0.750593, 0.0869031}, +{0.834327, 0.477193, -0.276017}, +{-0.73715, 0.67071, -0.082209}, +{0.406418, 0.207343, 0.88985}, +{-0.384989, -0.266046, -0.883744}, +{0.826796, 0.556442, -0.0823459}, +{0.27884, -0.526639, -0.803056}, +{0.345377, 0.934063, 0.0907744}, +{0.630473, 0.574372, 0.522112}, +{0.0634988, -0.426974, -0.902032}, +{0.165134, 0.137897, 0.976583}, +{0.507099, -0.0612336, 0.85971}, +{-0.188623, -0.954086, -0.232684}, +{-0.74464, 0.66742, 0.00787858}, +{0.948294, 0.296856, 0.112318}, +{0.907994, 0.0747335, 0.412264}, +{0.056506, -0.895198, -0.442073}, +{-0.767455, 0.588742, 0.253763}, +{-0.121601, -0.905084, -0.407475}, +{-0.644886, 0.138852, 0.75156}, +{-0.172007, 0.488813, 0.855263}, +{-0.165697, 0.98617, -0.00366394}, +{0.301881, -0.72227, 0.62225}, +{0.681361, 0.0795133, 0.727616}, +{-0.28768, -0.470468, 0.834206}, +{0.30044, 0.347393, 0.888287}, +{0.646263, 0.495182, -0.580636}, +{0.663723, 0.670265, 0.331991}, +{0.93092, -0.225786, 0.28707}, +{-0.70559, -0.191705, -0.682196}, +{-0.513263, -0.174103, -0.840386}, +{0.232144, -0.631352, 0.739935}, +{0.302295, 0.920832, 0.246348}, +{-0.748212, 0.519721, 0.412393}, +{-0.188031, 0.536649, -0.822589}, +{0.658181, -0.166369, -0.734247}, +{0.0335146, 0.96001, 0.277954}, +{-0.105593, -0.579084, -0.808401}, +{0.0975521, 0.832126, -0.54594}, +{0.0831698, 0.993836, -0.0733037}, +{0.170407, 0.630268, 0.757446}, +{-0.578746, -0.497588, 0.64611}, +{-0.102065, -0.404845, -0.908671}, +{0.0637224, 0.13331, 0.989024}, +{0.504746, 0.84157, 0.192334}, +{-0.785196, -0.0177103, 0.618994}, +{-0.0443331, 0.998739, -0.0235682}, +{0.0233999, 0.626399, -0.779152}, +{0.46265, -0.550107, 0.695225}, +{-0.466103, 0.873081, -0.143101}, +{-0.298378, 0.760344, -0.576929}, +{0.239922, 0.908985, 0.340856}, +{-0.370136, -0.318432, 0.872697}, +{0.991622, -0.123139, 0.0390237}, +{0.313605, 0.261912, 0.912718}, +{-0.658128, -0.266672, 0.704098}, +{0.435646, 0.396013, -0.808323}, +{0.812477, -0.294306, -0.503255}, +{-0.886333, 0.141658, -0.440848}, +{0.178769, -0.971434, -0.156066}, +{-0.243618, 0.212382, -0.946332}, +{0.327126, -0.582619, -0.744005}, +{-0.173727, 0.764477, 0.620801}, +{0.781104, 0.123967, 0.611971}, +{-0.657394, 0.475693, -0.584422}, +{-0.290956, 0.945802, 0.144232}, +{-0.772895, 0.270237, 0.574112}, +{-0.829433, 0.16689, 0.533093}, +{-0.48439, -0.658587, 0.575873}, +{-0.996646, 0.0631679, 0.0520179}, +{-0.0419412, 0.980592, 0.191522}, +{-0.256055, -0.913044, -0.317469}, +{0.449281, -0.447753, 0.773087}, +{0.879859, 0.466911, -0.0885525}, +{0.318438, 0.947914, -0.00753829}, +{0.695515, -0.558873, -0.451575}, +{0.250314, -0.963127, -0.0986358}, +{-0.186475, 0.17756, 0.966281}, +{0.521197, -0.825719, -0.215736}, +{0.123416, 0.909098, 0.397882}, +{0.982546, 0.18552, 0.0136277}, +{-0.364027, -0.00960808, -0.931339}, +{0.178895, 0.366162, -0.913193}, +{-0.881963, -0.0760474, 0.465143}, +{-0.498786, 0.394994, 0.771487}, +{0.390364, -0.694142, 0.6048}, +{0.95186, -0.237071, -0.194319}, +{0.446884, 0.886582, 0.119445}, +{-0.94342, 0.326027, -0.0605372}, +{0.439269, -0.831524, 0.340015}, +{0.0989651, -0.190992, -0.97659}, +{0.275829, -0.140987, 0.950811}, +{-0.65735, -0.530387, 0.535332}, +{-0.176912, -0.339826, 0.923699}, +{0.76408, 0.269737, 0.586023}, +{-0.821621, 0.0958077, -0.561926}, +{-0.903307, -0.427459, 0.0362622}, +{0.868501, 0.0484461, -0.493314}, +{0.338876, 0.154679, 0.928029}, +{-0.461641, -0.432368, -0.774562}, +{0.610247, 0.364629, -0.70331}, +{0.271983, 0.345405, 0.898176}, +{-0.762101, 0.61205, 0.21118}, +{0.873855, 0.16304, -0.458034}, +{0.760497, -0.611354, -0.21884}, +{0.162009, 0.826212, 0.539561}, +{0.294726, -0.953332, -0.0655345}, +{-0.113676, -0.207936, 0.971514}, +{0.13588, 0.70142, 0.699676}, +{-0.68623, 0.359019, -0.632609}, +{-0.101776, -0.567788, 0.816859}, +{-0.417525, 0.906281, 0.0657799}, +{-0.89466, 0.188982, -0.404807}, +{-0.0161445, -0.333349, 0.942665}, +{-0.355301, -0.0211159, 0.934513}, +{0.299065, -0.909342, -0.289236}, +{-0.581135, -0.764344, -0.279393}, +{0.808778, -0.461172, -0.364963}, +{-0.623037, 0.758066, 0.192772}, +{-0.231193, -0.399469, 0.887116}, +{0.989199, 0.0886143, -0.116757}, +{-0.928657, 0.364705, -0.0677241}, +{0.65954, -0.209063, 0.722011}, +{-0.562258, -0.826759, -0.0183272}, +{0.794261, -0.370864, -0.481258}, +{-0.767887, 0.0576649, -0.637984}, +{-0.277037, 0.178042, 0.94422}, +{-0.975932, 0.104137, -0.191602}, +{0.407482, -0.490155, 0.770523}, +{-0.0060236, -0.277442, -0.960724}, +{0.59114, 0.786571, 0.178495}, +{-0.117313, 0.652223, -0.748895}, +{-0.541011, 0.144298, -0.828544}, +{-0.007412, -0.993, 0.11788}, +{0.372345, -0.526191, -0.764515}, +{0.679281, -0.713138, -0.173242}, +{0.228427, -0.971777, -0.0589166}, +{0.164661, 0.950704, -0.262769}, +{-0.217413, -0.862799, -0.45641}, +{-0.316876, 0.799573, -0.510169}, +{0.24787, -0.442011, -0.862082}, +{0.0327078, -0.221394, 0.974636}, +{-0.596142, 0.560674, -0.574682}, +{-0.313343, -0.70858, 0.632243}, +{-0.637126, -0.294744, -0.712177}, +{0.057382, -0.687515, -0.723899}, +{-0.73276, -0.67584, -0.0793936}, +{0.876699, -0.274034, 0.395353}, +{-0.922578, -0.129773, -0.36333}, +{-0.238693, 0.219974, 0.945853}, +{-0.815283, -0.0161969, -0.578836}, +{0.190955, 0.282583, 0.940044}, +{-0.953942, -0.215436, -0.208762}, +{0.788527, -0.45317, 0.415768}, +{0.545382, -0.739098, -0.395339}, +{0.553411, -0.779192, 0.294272}, +{-0.882816, -0.456382, -0.111136}, +{-0.92995, 0.349152, 0.115267}, +{-0.34724, -0.688126, -0.637109}, +{-0.0473178, -0.97814, -0.202493}, +{0.823553, -0.356671, 0.441073}, +{-0.0622451, -0.589876, 0.805091}, +{0.883198, 0.462455, 0.0780825}, +{-0.1316, 0.919219, -0.371102}, +{-0.950104, 0.255609, 0.17879}, +{-0.400712, -0.905228, 0.141394}, +{-0.675956, -0.0139011, -0.736811}, +{0.980282, -0.120089, 0.156926}, +{-0.624136, -0.172314, -0.762078}, +{0.00832381, 0.205654, -0.978589}, +{-0.664602, 0.739198, 0.109045}, +{-0.478563, -0.0841581, -0.874011}, +{-0.294034, 0.87857, 0.376376}, +{-0.51573, 0.794134, 0.321517}, +{-0.269378, 0.837387, -0.475625}, +{-0.446448, -0.757962, 0.475582}, +{-0.167328, -0.807857, 0.565127}, +{0.698802, -0.342471, -0.628004}, +{-0.519797, -0.776475, -0.356227}, +{0.110853, -0.138104, 0.984195}, +{0.636723, 0.137819, -0.758677}, +{-0.0459099, -0.398683, 0.915939}, +{-0.206355, -0.974313, -0.0901763}, +{0.948026, -0.137482, -0.286959}, +{0.208742, 0.131562, 0.969081}, +{-0.524804, -0.82482, 0.210361}, +{-0.287829, 0.644375, 0.708474}, +{0.211453, 0.977131, 0.0224358}, +{-0.973484, 0.228511, -0.0105757}, +{-0.0564095, 0.801855, 0.59485}, +{-0.128017, -0.764244, -0.632093}, +{-0.0114613, -0.807373, -0.58993}, +{0.513999, -0.328332, 0.792467}, +{-0.542349, 0.82885, 0.137349}, +{0.810354, 0.573412, 0.120518}, +{0.829778, 0.211053, -0.516647}, +{0.0495371, -0.392208, 0.918542}, +{-0.133149, 0.764289, -0.630978}, +{-0.593654, 0.464426, 0.657178}, +{-0.713579, 0.227889, -0.662474}, +{0.511538, -0.0171741, 0.859089}, +{-0.131576, 0.207652, -0.969313}, +{-0.238944, -0.769021, 0.592884}, +{-0.569536, -0.193796, -0.798794}, +{-0.0219794, 0.151472, -0.988217}, +{-0.712505, 0.0432761, -0.700331}, +{-0.302544, 0.426474, -0.852401}, +{0.844624, 0.429792, 0.3192}, +{-0.369507, 0.114876, 0.9221}, +{0.891473, -0.453073, 0.00105579}, +{-0.764008, 0.560936, 0.318816}, +{0.154022, -0.828932, 0.537726}, +{-0.858018, -0.504986, -0.0937793}, +{0.0318043, 0.510316, -0.859398}, +{0.284693, -0.937835, 0.198532}, +{-0.908086, -0.27243, -0.318058}, +{0.416079, 0.892522, -0.174018}, +{0.557645, 0.290139, 0.777722}, +{0.517932, -0.204962, -0.830504}, +{0.450044, -0.892486, -0.030502}, +{0.521959, -0.506421, 0.686365}, +{-0.0398415, -0.0492758, -0.99799}, +{-0.578023, 0.282477, -0.76557}, +{0.991716, -0.125238, -0.0285308}, +{0.997967, 0.05386, -0.0340607}, +{-0.500025, 0.830427, -0.245694}, +{-0.678151, 0.698175, 0.229486}, +{0.230158, 0.713623, -0.661641}, +{-0.253369, 0.729289, 0.635564}, +{0.416016, -0.9093, 0.0102363}, +{0.179937, -0.376593, -0.908736}, +{0.464963, 0.781267, -0.416452}, +{-0.223779, 0.584169, -0.780173}, +{-0.93953, 0.168826, 0.297962}, +{-0.794249, -0.569984, 0.210442}, +{0.044083, -0.49422, 0.868218}, +{0.427833, 0.546391, 0.720011}, +{-0.485778, -0.819769, -0.303312}, +{0.134052, -0.319738, -0.937975}, +{0.0610441, -0.910343, 0.409328}, +{-0.527754, 0.515239, 0.675281}, +{-0.262446, -0.956522, -0.127232}, +{0.741416, -0.254652, -0.620851}, +{-0.975139, -0.0212825, 0.22057}, +{-0.247109, 0.968391, -0.0339873}, +{0.657493, 0.616271, -0.433489}, +{-0.894538, 0.33301, 0.29817}, +{-0.700295, 0.588437, -0.40414}, +{-0.296899, 0.174579, 0.938815}, +{-0.647565, 0.619627, -0.443534}, +{0.956024, -0.206718, 0.208054}, +{-0.982597, -0.178311, 0.052037}, +{-0.59263, -0.684524, -0.424519}, +{0.849663, -0.414869, 0.32551}, +{0.245603, 0.968854, 0.0316425}, +{-0.718662, -0.670121, -0.18564}, +{0.235585, -0.624247, -0.744859}, +{-0.888594, -0.411102, 0.203458}, +{0.0573747, 0.390128, -0.918971}, +{0.897284, -0.0464668, 0.439001}, +{-0.424192, -0.400426, 0.812232}, +{0.390234, -0.0315722, 0.920174}, +{0.384768, 0.92236, -0.0347141}, +{0.534395, 0.181384, 0.825543}, +{0.272106, -0.224966, -0.935601}, +{0.772463, 0.188939, -0.606302}, +{-0.433775, -0.860076, -0.26853}, +{0.765406, 0.334075, 0.550043}, +{0.601338, 0.780836, 0.169376}, +{0.680769, 0.390947, -0.619447}, +{-0.240362, 0.109095, -0.964533}, +{0.837329, 0.306345, -0.452805}, +{-0.0516856, -0.823669, 0.56471}, +{0.479991, -0.482635, -0.732579}, +{0.00157147, -0.979391, 0.201969}, +{-0.86929, -0.350078, 0.348969}, +{-0.879117, 0.269414, -0.393154}, +{0.332893, -0.201929, 0.92109}, +{0.171121, -0.77776, 0.604819}, +{0.885766, 0.0188958, 0.463748}, +{0.524661, 0.588337, -0.615296}, +{-0.941107, 0.326086, 0.0893619}, +{0.169901, -0.122705, 0.977792}, +{-0.649489, 0.412738, -0.638601}, +{0.38914, 0.874108, 0.290697}, +{0.632782, -0.162629, 0.757059}, +{-0.980766, -0.13467, 0.141285}, +{-0.583803, -0.23593, 0.77686}, +{0.0387273, 0.94605, 0.321698}, +{-0.30302, -0.952445, 0.0320424}, +{0.451186, 0.891939, -0.0295882}, +{-0.423095, -0.724701, -0.543875}, +{0.665562, 0.435529, 0.606087}, +{0.798308, 0.598062, 0.0709049}, +{-0.852589, 0.495437, -0.166234}, +{-0.531388, 0.742556, 0.407723}, +{-0.532764, -0.84609, 0.0171641}, +{0.253853, -0.576223, -0.776869}, +{0.459048, -0.845309, -0.273364}, +{-0.474264, -0.0617456, 0.878215}, +{0.370301, -0.690091, -0.621813}, +{-0.755848, -0.269123, 0.596881}, +{0.0626481, -0.978606, -0.195971}, +{0.401462, 0.658984, 0.636057}, +{0.66764, -0.729175, 0.150204}, +{-0.897582, -0.439475, -0.0347703}, +{-0.519814, 0.235925, -0.821056}, +{-0.959402, 0.0691895, 0.273423}, +{-0.355833, 0.933777, 0.038001}, +{-0.164163, 0.986093, 0.0259026}, +{-0.0971891, -0.307116, 0.946697}, +{-0.625688, 0.0707773, 0.776856}, +{-0.951891, 0.278931, -0.12689}, +{0.343301, -0.727641, 0.593872}, +{0.757289, 0.548472, -0.354531}, +{0.94048, -0.179744, -0.288427}, +{0.82629, -0.492907, 0.272557}, +{-0.373644, 0.927313, -0.0219171}, +{-0.0148258, -0.774137, -0.632845}, +{0.474066, -0.843669, -0.251959}, +{0.386212, 0.922322, 0.0127142}, +{-0.0610782, 0.722745, 0.68841}, +{-0.876126, -0.00894416, 0.482}, +{0.378613, 0.916956, -0.125872}, +{-0.69009, 0.618916, 0.375125}, +{0.92193, 0.339139, -0.187163}, +{-0.083078, -0.231661, -0.969243}, +{-0.701255, -0.7125, 0.0241949}, +{-0.988043, 0.0487609, 0.146264}, +{0.255898, 0.514707, -0.818287}, +{-0.412533, -0.24059, -0.878597}, +{-0.518503, -0.52478, 0.6751}, +{-0.883045, 0.465878, 0.0564691}, +{0.491264, -0.665626, -0.561785}, +{-0.95865, 0.209004, -0.193152}, +{-0.0412347, 0.92731, 0.372016}, +{0.324157, 0.250873, -0.912132}, +{-0.248113, -0.228636, -0.941364}, +{-0.178278, -0.809909, -0.558806}, +{0.717251, -0.691423, -0.0865183}, +{-0.950332, 0.105907, -0.292664}, +{0.220015, 0.372745, 0.901474}, +{-0.876851, 0.055191, 0.477583}, +{-0.613126, -0.362667, 0.701819}, +{-0.66855, 0.0541595, 0.741692}, +{-0.152583, 0.463355, 0.872938}, +{-0.0526863, 0.119205, -0.991471}, +{0.848029, 0.459467, 0.264078}, +{0.360142, 0.932178, 0.0366424}, +{0.785941, 0.412695, -0.460412}, +{-0.108827, -0.39922, 0.910374}, +{0.966445, 0.0628985, 0.249053}, +{0.0200588, 0.657105, -0.753532}, +{-0.226779, 0.713063, 0.66341}, +{0.550349, -0.632884, -0.544585}, +{-0.0762218, 0.892488, -0.444585}, +{0.555455, -0.0191556, -0.831326}, +{0.10247, -0.330611, -0.938188}, +{0.704237, -0.425319, 0.568466}, +{0.143212, 0.33662, -0.930687}, +{-0.2142, -0.548569, -0.808202}, +{-0.0272693, 0.080357, -0.996393}, +{0.000371446, 0.726728, 0.686925}, +{-0.461627, -0.683031, -0.566012}, +{-0.939787, 0.0438012, 0.338942}, +{0.365693, -0.790935, 0.490602}, +{-0.97862, -0.191111, 0.0760241}, +{-0.919294, -0.0535573, 0.38991}, +{0.00754552, 0.0862503, 0.996245}, +{-0.79431, 0.586817, -0.157218}, +{0.688349, -0.452255, 0.567135}, +{0.136166, -0.905463, 0.401989}, +{-0.0166253, 0.586713, -0.809624}, +{0.411559, -0.833245, -0.369218}, +{0.223706, -0.0589692, 0.972871}, +{-0.796068, -0.466791, -0.385204}, +{-0.653786, 0.60573, -0.453491}, +{-0.268202, 0.963257, -0.0142502}, +{0.271745, 0.609036, 0.745137}, +{-0.357006, 0.314725, -0.879486}, +{-0.151997, -0.902489, 0.403001}, +{0.506257, 0.129592, -0.85259}, +{-0.020311, 0.387045, -0.921837}, +{-0.582477, -0.0412141, 0.811802}, +{-0.521465, 0.478639, -0.706385}, +{-0.296019, -0.435698, 0.850023}, +{-0.395479, -0.803993, -0.444063}, +{-0.175006, 0.937929, -0.299437}, +{0.838746, -0.544521, -0.00157376}, +{-0.110255, -0.0238806, -0.993616}, +{-0.667121, 0.712781, -0.216548}, +{0.299845, 0.920852, 0.249247}, +{-0.0515077, -0.99867, -0.00232009}, +{0.178735, -0.864259, 0.470223}, +{-0.468325, -0.874645, -0.125169}, +{0.852041, -0.276423, 0.44454}, +{-0.760551, 0.0322131, 0.648479}, +{0.409334, 0.318195, -0.855101}, +{0.608594, 0.0680627, -0.790557}, +{0.567129, 0.127259, -0.813738}, +{0.656535, -0.0345353, -0.753505}, +{0.877005, 0.226115, -0.42395}, +{-0.914003, -0.360397, -0.186311}, +{0.906787, -0.185747, 0.378465}, +{-0.842751, 0.412417, 0.345952}, +{0.744344, 0.664736, 0.0638628}, +{0.123714, 0.755327, -0.643565}, +{-0.989819, -0.117476, -0.0803607}, +{0.812842, -0.37025, 0.449669}, +{0.359762, -0.673066, -0.646184}, +{0.563714, 0.67623, 0.474277}, +{-0.32673, 0.365413, 0.87162}, +{0.355862, 0.851444, 0.385234}, +{-0.444099, 0.884856, -0.140734}, +{-0.518212, 0.275016, 0.809829}, +{-0.965081, -0.206953, -0.160588}, +{-0.48539, -0.693924, -0.531851}, +{0.310805, -0.807856, 0.500768}, +{0.521097, -0.797128, -0.305032}, +{0.806306, 0.291275, 0.514811}, +{-0.215554, -0.77776, 0.590446}, +{0.457502, 0.734079, -0.501817}, +{-0.606388, -0.657832, -0.446711}, +{-0.438057, -0.469783, -0.766427}, +{0.0512557, 0.632316, 0.773013}, +{-0.984274, 0.00527919, -0.176571}, +{0.707198, 0.619491, 0.340737}, +{-0.534898, -0.803109, 0.26249}, +{0.875431, 0.027402, 0.482565}, +{-0.722138, 0.668092, 0.179358}, +{-0.840659, 0.454499, 0.294487}, +{-0.36831, -0.836943, 0.404813}, +{-0.430301, 0.295783, -0.85285}, +{0.962924, 0.211972, 0.166867}, +{-0.125782, 0.0494547, 0.990824}, +{-0.0715944, -0.720002, 0.690269}, +{-0.0774431, 0.924758, -0.372594}, +{-0.806037, 0.462507, 0.369312}, +{-0.97373, 0.168624, 0.15302}, +{-0.536722, -0.834074, 0.127474}, +{-0.711738, -0.694911, -0.102603}, +{0.747344, 0.646129, 0.154903}, +{0.562866, -0.610824, 0.556845}, +{0.489621, 0.864246, -0.115543}, +{0.504884, 0.400291, -0.764761}, +{-0.948931, -0.301603, 0.0925529}, +{0.356807, 0.831749, 0.425302}, +{-0.317318, -0.89298, 0.319212}, +{0.768889, -0.458509, -0.445622}, +{-0.279466, -0.322849, 0.904249}, +{-0.978233, 0.191251, -0.0805206}, +{0.657112, 0.699075, -0.281954}, +{0.469821, 0.71135, 0.522732}, +{-0.92368, 0.247636, -0.29239}, +{0.497268, 0.75243, 0.431942}, +{-0.880444, 0.394607, 0.262878}, +{0.958675, 0.281137, 0.0436421}, +{0.817114, 0.576298, -0.0142972}, +{0.994933, 0.027068, 0.0968282}, +{-0.88562, -0.462646, 0.0404601}, +{-0.107159, -0.242041, -0.96433}, +{-0.91559, -0.394154, -0.0796051}, +{0.0940987, 0.96829, 0.231429}, +{-0.769799, -0.578461, 0.2698}, +{0.802056, 0.379614, 0.461086}, +{0.0948483, -0.986924, 0.130327}, +{0.959752, 0.264571, -0.0942274}, +{0.686637, -0.69076, -0.226673}, +{-0.0111353, 0.886364, 0.462856}, +{-0.687004, 0.530813, -0.49625}, +{-0.062462, -0.60022, -0.797392}, +{-0.907758, -0.418255, 0.0322052}, +{-0.940578, 0.301108, 0.156994}, +{0.413253, -0.908737, 0.0584695}, +{0.884226, -0.464548, -0.0483708}, +{0.637176, -0.344461, -0.689459}, +{-0.278267, -0.707751, -0.649351}, +{0.51137, 0.549188, 0.660979}, +{0.483002, -0.853932, -0.193672}, +{-0.557307, 0.628028, 0.54313}, +{0.894427, 0.0714784, 0.441464}, +{-0.0610692, -0.937543, -0.342466}, +{0.933624, -0.358155, 0.00843864}, +{-0.473702, -0.850981, -0.226801}, +{-0.155743, -0.98218, 0.1052}, +{-0.480714, 0.117182, 0.869012}, +{0.461441, 0.595996, -0.657161}, +{0.391121, -0.162678, 0.905848}, +{0.410735, 0.564103, -0.716299}, +{-0.226095, 0.88988, -0.396225}, +{0.388617, -0.154197, 0.908405}, +{0.577155, 0.478368, 0.661858}, +{-0.336802, -0.83204, -0.440765}, +{-0.334935, 0.913934, -0.229225}, +{0.728244, 0.0963185, -0.678516}, +{0.223291, -0.306122, -0.925435}, +{-0.329333, 0.744224, -0.581094}, +{0.587694, -0.362453, -0.723356}, +{-0.916875, 0.269037, 0.294889}, +{-0.264973, 0.679082, -0.684571}, +{0.782591, 0.0627382, -0.619366}, +{0.307547, 0.938237, -0.158514}, +{-0.559498, -0.348747, -0.75189}, +{0.195437, 0.138862, -0.970836}, +{0.203751, -0.945658, -0.253409}, +{0.531166, -0.357809, -0.768008}, +{-0.41101, -0.0314073, 0.911089}, +{-0.886193, 0.0153088, 0.463063}, +{-0.508897, -0.733116, -0.451181}, +{-0.885053, -0.0494792, -0.462854}, +{0.00193451, -0.478163, 0.878269}, +{0.289907, -0.458413, 0.840126}, +{-0.747995, -0.499798, 0.436698}, +{-0.384747, 0.922892, 0.0155168}, +{-0.406349, -0.913352, 0.0258396}, +{0.553947, 0.394189, 0.73332}, +{-0.407247, -0.871514, 0.273155}, +{-0.132593, 0.990013, 0.0478859}, +{0.151822, 0.233219, -0.960499}, +{-0.769176, 0.188715, 0.610536}, +{0.845749, 0.193445, 0.49728}, +{0.120775, -0.960782, 0.249622}, +{-0.318443, -0.76547, -0.559151}, +{0.51338, -0.858148, 0.00483224}, +{-0.78329, -0.525549, -0.332047}, +{-0.619844, -0.0336408, -0.784004}, +{0.525485, -0.739414, 0.420871}, +{0.643664, 0.561112, -0.520432}, +{-0.384694, -0.516673, -0.764892}, +{0.27667, 0.109823, 0.954669}, +{0.530839, 0.808903, 0.252758}, +{-0.0444572, -0.800562, -0.597598}, +{0.647347, 0.67988, -0.344537}, +{0.62075, -0.737366, -0.266386}, +{0.307871, -0.934327, -0.179576}, +{-0.721049, 0.673506, -0.162722}, +{0.456558, -0.0961293, 0.884485}, +{0.533682, -0.784538, 0.315726}, +{-0.0623512, 0.943435, -0.325641}, +{0.541308, 0.612609, 0.57593}, +{0.417872, -0.0060313, 0.908486}, +{0.00231839, -0.999821, 0.018791}, +{0.00322016, 0.491524, 0.870858}, +{-0.909238, -0.403173, 0.103622}, +{0.0617331, -0.548717, -0.833726}, +{-0.177169, -0.0902435, 0.980034}, +{-0.0151642, -0.956304, -0.291979}, +{-0.450303, 0.87543, 0.175639}, +{0.80663, -0.184426, 0.561547}, +{-0.172279, 0.951073, 0.256477}, +{0.507493, 0.183276, -0.841938}, +{0.997109, -0.00537923, -0.0757891}, +{0.880381, -0.401582, -0.252313}, +{-0.543401, -0.822325, 0.168812}, +{0.668734, 0.154025, -0.727372}, +{0.522773, 0.554341, 0.647622}, +{-0.117, 0.826837, -0.550138}, +{-0.903178, 0.372345, -0.213608}, +{-0.659975, -0.688639, -0.30035}, +{0.793478, -0.35465, -0.494586}, +{0.40331, 0.779916, 0.478615}, +{0.375859, 0.310938, 0.872953}, +{-0.208992, 0.0900884, -0.973759}, +{0.0698416, 0.725491, -0.684678}, +{-0.0041568, -0.521491, -0.853247}, +{0.827178, 0.423802, -0.369011}, +{-0.384463, -0.792356, 0.473667}, +{-0.762633, 0.013385, 0.646693}, +{0.0565688, 0.830027, -0.554848}, +{-0.540391, -0.63673, -0.550048}, +{0.534055, -0.836366, 0.123599}, +{0.613649, -0.0997385, -0.783254}, +{0.759973, 0.504826, 0.40938}, +{-0.466641, 0.299014, 0.832368}, +{0.597598, 0.503513, 0.623981}, +{-0.966721, -0.130458, 0.22007}, +{0.832234, -0.263242, -0.487945}, +{0.940425, -0.212844, 0.26514}, +{0.942508, -0.147517, 0.299861}, +{0.000406871, -0.96521, 0.261476}, +{0.110147, -0.772508, 0.625379}, +{-0.52056, -0.349817, -0.778875}, +{0.874266, -0.460366, -0.154021}, +{-0.773935, -0.44889, 0.446679}, +{-0.0855314, 0.376728, 0.922367}, +{0.586712, 0.575861, 0.569344}, +{0.327957, 0.935052, -0.134616}, +{-0.00718766, 0.862041, -0.506787}, +{0.9695, -0.15598, 0.189048}, +{-0.138613, 0.89377, -0.426569}, +{-0.631534, 0.419288, 0.652198}, +{-0.72035, -0.195532, 0.66548}, +{0.0446501, 0.184788, -0.981764}, +{-0.783654, 0.100316, -0.613044}, +{0.577455, 0.777446, 0.249245}, +{0.462889, -0.858585, 0.220376}, +{-0.841462, -0.384684, -0.379421}, +{-0.863628, 0.503862, 0.0164279}, +{0.339621, -0.826651, -0.44867}, +{0.220399, 0.0245116, 0.975102}, +{-0.543591, 0.188817, 0.817836}, +{0.16243, -0.957113, -0.239899}, +{0.876263, 0.399185, -0.26984}, +{0.179428, 0.316028, -0.931629}, +{0.115207, 0.740121, 0.662532}, +{-0.614899, 0.221476, 0.756867}, +{0.0361584, 0.187868, 0.981529}, +{-0.0958132, 0.575863, -0.811913}, +{0.389227, -0.338505, -0.85669}, +{0.85373, -0.0366796, -0.519423}, +{-0.902228, -0.359186, 0.238684}, +{-0.247044, 0.952445, -0.178375}, +{-0.219057, -0.878689, 0.42417}, +{-0.382045, -0.0716048, -0.921365}, +{-0.00891314, -0.992079, -0.125302}, +{-0.343547, 0.86154, -0.373797}, +{0.769489, -0.399872, 0.497985}, +{0.834522, 0.033065, -0.549981}, +{0.0293713, 0.999506, 0.0111891}, +{0.767002, 0.576767, 0.281156}, +{0.617512, -0.440566, -0.651599}, +{0.858539, 0.184531, 0.478392}, +{0.735985, -0.0660194, -0.673771}, +{0.169436, 0.83516, 0.523258}, +{-0.971435, -0.100976, -0.21475}, +{-0.529143, 0.385999, -0.755653}, +{-0.26917, 0.296692, 0.916254}, +{0.573012, 0.638289, -0.514046}, +{-0.959353, 0.186178, -0.212083}, +{0.754692, 0.501912, -0.42252}, +{-0.44849, -0.730648, 0.514791}, +{-0.263596, -0.898799, 0.350254}, +{0.174097, -0.900129, -0.399322}, +{-0.641445, -0.523962, -0.560368}, +{0.338258, -0.907212, -0.250094}, +{0.299755, 0.306285, 0.903513}, +{-0.280233, 0.505717, -0.815917}, +{-0.507261, -0.696009, 0.508191}, +{-0.180303, 0.599335, 0.779929}, +{-0.256069, -0.783036, -0.566819}, +{0.392981, -0.850156, -0.35043}, +{-0.496584, -0.863631, -0.0868624}, +{0.660183, -0.751064, 0.00783591}, +{-0.615304, -0.713558, -0.335015}, +{0.183484, 0.938736, 0.291732}, +{0.630575, -0.255534, -0.732856}, +{0.370447, -0.904492, -0.211337}, +{0.665919, 0.574484, -0.475941}, +{-0.00951968, 0.796251, -0.604892}, +{-0.8817, 0.451421, -0.137205}, +{0.156115, -0.924411, -0.347983}, +{-0.180785, 0.95709, -0.226486}, +{0.310889, -0.0509561, -0.949079}, +{-0.00859597, 0.259077, -0.965818}, +{0.976049, 0.159106, -0.148371}, +{-0.245468, 0.964076, -0.101498}, +{-0.368103, 0.838402, 0.401974}, +{0.0955832, 0.672777, 0.733645}, +{-0.598686, 0.451949, -0.6613}, +{-0.258922, 0.638277, 0.724956}, +{-0.540547, 0.454222, 0.70816}, +{0.271563, -0.0873829, 0.958446}, +{0.219411, -0.685216, 0.694505}, +{-0.102656, -0.933706, -0.343008}, +{0.786511, -0.00580375, 0.617549}, +{0.364143, 0.588379, -0.721949}, +{0.36775, -0.313753, 0.875396}, +{0.947864, -0.317536, -0.0269145}, +{0.939337, 0.337765, -0.0596795}, +{0.568666, -0.538615, 0.621701}, +{0.748828, -0.644929, 0.152719}, +{0.523342, -0.24516, 0.816094}, +{0.733229, 0.586715, -0.343718}, +{-0.797999, -0.542301, -0.262881}, +{0.671106, -0.531998, -0.516328}, +{-0.888974, -0.301074, -0.345079}, +{0.206924, 0.550319, 0.808907}, +{-0.559181, 0.289254, 0.776948}, +{-0.0938298, -0.697217, -0.710693}, +{0.623535, 0.523176, 0.580939}, +{-0.707511, 0.062856, 0.703902}, +{-0.646637, -0.700816, 0.301194}, +{-0.525121, 0.737893, -0.423984}, +{0.15164, 0.988025, 0.0284771}, +{-0.86092, 0.179796, -0.475909}, +{-0.639724, 0.648446, -0.412638}, +{-0.997179, -0.0743239, -0.0104461}, +{-0.117534, -0.518366, -0.847043}, +{-0.347103, -0.28328, -0.89402}, +{0.0708557, 0.695661, -0.714867}, +{-0.45203, -0.841223, 0.296669}, +{-0.111283, 0.989303, -0.0943161}, +{-0.0670789, -0.984849, 0.159915}, +{0.586661, -0.191435, 0.786881}, +{0.0423296, -0.630171, -0.775301}, +{-0.316093, -0.812612, 0.48964}, +{0.874997, -0.398802, -0.274477}, +{0.506497, 0.79839, 0.32563}, +{0.084214, -0.532482, 0.842242}, +{0.907691, 0.182187, -0.378029}, +{-0.126219, 0.0951336, 0.98743}, +{0.0281629, 0.923537, -0.382475}, +{-0.178284, 0.724697, 0.665604}, +{0.747961, -0.653504, 0.116135}, +{0.325684, 0.0777026, 0.94228}, +{-0.674231, 0.152116, -0.722685}, +{0.662966, 0.351887, -0.660796}, +{0.285848, -0.74964, -0.596934}, +{0.216846, 0.699646, 0.680789}, +{-0.771803, 0.331673, -0.542506}, +{-0.627113, -0.77026, -0.115883}, +{0.389815, -0.818306, -0.422397}, +{0.238385, -0.868329, 0.434944}, +{-0.999817, 0.0161285, 0.0103098}, +{-0.904934, 0.39839, -0.149598}, +{-0.185575, -0.0850114, 0.978946}, +{0.131874, 0.487958, -0.862848}, +{-0.174371, 0.718704, -0.673097}, +{-0.119936, -0.846542, 0.518634}, +{0.097234, 0.896331, -0.432592}, +{-0.832144, -0.265247, -0.487012}, +{0.515418, 0.0406872, 0.855973}, +{0.978624, 0.033505, -0.202908}, +{-0.272298, 0.348537, -0.89687}, +{0.59168, 0.638328, 0.492394}, +{0.840538, 0.365087, 0.400259}, +{0.77142, 0.636009, -0.0200682}, +{0.977858, -0.12861, -0.165087}, +{0.847231, 0.19858, 0.492713}, +{0.438245, -0.614395, 0.656095}, +{-0.650568, -0.74577, -0.143489}, +{0.0600391, -0.283202, -0.957179}, +{0.102575, -0.53136, -0.840913}, +{0.972715, 0.126757, -0.194316}, +{0.456117, 0.889347, 0.0319306}, +{-0.159025, -0.896541, -0.413431}, +{0.691973, -0.701302, -0.171316}, +{-0.633769, -0.676413, -0.375236}, +{0.320356, -0.942328, 0.0969067}, +{-0.921228, -0.387783, 0.0310236}, +{-0.342873, 0.204252, 0.916907}, +{-0.247699, 0.914163, 0.320859}, +{0.734626, 0.556813, -0.387664}, +{0.475471, -0.363521, -0.801112}, +{0.693229, -0.0917919, -0.714848}, +{0.379962, -0.746411, -0.54635}, +{0.383612, -0.551422, 0.740794}, +{-0.620745, 0.754373, 0.213533}, +{-0.605588, -0.743471, 0.283748}, +{0.231051, -0.972424, 0.0317357}, +{-0.812267, -0.524599, 0.254988}, +{-0.718837, 0.461949, -0.519496}, +{0.0850343, -0.238328, 0.967455}, +{-0.697359, -0.44253, -0.563789}, +{-0.557382, -0.332204, 0.760898}, +{-0.658554, -0.720385, 0.217605}, +{0.868968, -0.256146, -0.423419}, +{0.745286, 0.43461, 0.505631}, +{-0.419673, -0.694695, -0.584186}, +{0.0133172, 0.437802, -0.898973}, +{-0.540184, -0.0200985, -0.841307}, +{0.00857848, -0.0758923, -0.997079}, +{0.42252, -0.430103, 0.797802}, +{0.258198, 0.921777, -0.289243}, +{0.453887, -0.775313, 0.439176}, +{-0.481425, 0.792202, 0.375027}, +{-0.908885, -0.215002, -0.357355}, +{0.880371, 0.333997, 0.336739}, +{-0.685781, 0.724493, 0.0693802}, +{0.75287, -0.521899, 0.401008}, +{-0.555896, -0.584391, -0.591157}, +{0.926853, -0.0787792, 0.367067}, +{0.599096, 0.790032, 0.130129}, +{-0.0800299, -0.961268, 0.26374}, +{-0.105976, 0.132551, -0.985494}, +{-0.779735, 0.00896166, -0.626045}, +{-0.362426, -0.772916, 0.520814}, +{0.0904422, -0.442068, 0.89241}, +{0.0510809, 0.150643, -0.987268}, +{0.534503, -0.457381, 0.71071}, +{0.379797, 0.207182, 0.901571}, +{0.832241, -0.0720661, 0.54971}, +{-0.64432, 0.30923, -0.699448}, +{0.562336, -0.646135, 0.516031}, +{-0.98079, -0.194263, 0.0176693}, +{-0.762813, -0.645052, -0.0449845}, +{-0.0507844, -0.312343, -0.948611}, +{-0.489643, 0.629634, -0.603167}, +{-0.307913, 0.566793, 0.764156}, +{0.649206, 0.425418, -0.630517}, +{-0.96375, 0.2654, -0.0273566}, +{0.441283, -0.87316, 0.20703}, +{0.2193, 0.686705, -0.693069}, +{-0.631308, 0.222781, 0.742845}, +{-0.253389, 0.702809, 0.664721}, +{0.462036, -0.860391, -0.21506}, +{0.413191, -0.408262, 0.814}, +{-0.752506, 0.466437, 0.464942}, +{0.912789, -0.357676, 0.197191}, +{-0.763851, 0.566754, -0.308742}, +{0.356875, -0.747708, 0.559975}, +{0.632369, 0.416689, 0.653054}, +{-0.394489, 0.758273, 0.519038}, +{0.641198, -0.739367, 0.205429}, +{0.924303, -0.0257618, 0.38079}, +{-0.236468, -0.350615, 0.906175}, +{0.676791, 0.707813, -0.202373}, +{0.966935, -0.0375242, 0.252246}, +{-0.850229, -0.443346, 0.283823}, +{-0.396208, 0.80668, -0.438504}, +{-0.711691, -0.690115, 0.131288}, +{-0.919239, -0.367192, -0.142021}, +{0.446242, 0.451553, -0.772637}, +{-0.777904, -0.15271, -0.609545}, +{-0.0499683, -0.752191, 0.657047}, +{-0.515899, -0.833077, -0.199578}, +{-0.137638, -0.618067, 0.773982}, +{0.386184, 0.590225, 0.70887}, +{0.441418, 0.739932, -0.507594}, +{0.801207, -0.271621, 0.533188}, +{-0.724752, -0.666212, -0.175772}, +{0.484562, -0.739167, -0.467796}, +{0.151947, -0.770387, 0.619207}, +{0.807826, 0.53662, 0.243836}, +{0.0160061, -0.228664, -0.973374}, +{-0.0725785, -0.544313, 0.835737}, +{-0.598839, 0.0976366, 0.794895}, +{0.132611, 0.466952, -0.874283}, +{0.62406, -0.52951, -0.574602}, +{-0.591797, 0.533061, -0.604667}, +{0.922513, -0.382425, 0.0521688}, +{-0.757578, -0.175438, 0.628727}, +{0.0763379, 0.737294, 0.671245}, +{-0.908078, -0.0915811, 0.408665}, +{0.590709, 0.590845, -0.549513}, +{-0.114357, 0.907418, 0.40437}, +{0.0515411, 0.891906, 0.449274}, +{-0.467265, 0.638907, 0.611115}, +{-0.213032, 0.854247, 0.474214}, +{-0.274709, 0.949345, -0.152574}, +{0.0149963, -0.836571, -0.547654}, +{-0.300591, -0.898605, -0.319616}, +{0.0898673, -0.960913, -0.261858}, +{-0.554931, 0.489267, -0.672807}, +{0.631752, 0.613657, 0.473618}, +{0.581176, 0.508307, -0.635499}, +{-0.309478, -0.918809, -0.244978}, +{-0.876036, -0.227938, 0.424976}, +{0.206989, -0.435035, 0.876299}, +{0.885297, 0.21622, 0.411701}, +{-0.521121, -0.761056, -0.386297}, +{0.54457, -0.156297, 0.824023}, +{-0.347625, 0.276678, -0.895883}, +{0.74808, -0.348012, -0.565035}, +{-0.965178, -0.0476816, -0.257212}, +{-0.336649, 0.266991, 0.902986}, +{-0.0882738, -0.976907, 0.194578}, +{-0.268958, 0.228098, -0.935753}, +{-0.182542, -0.710819, -0.679275}, +{-0.112118, 0.403014, -0.9083}, +{0.936329, 0.300868, 0.181016}, +{-0.181525, -0.971769, 0.150712}, +{-0.277529, -0.883986, 0.376226}, +{-0.700384, -0.0163547, 0.713579}, +{0.702325, 0.633504, -0.324673}, +{0.527775, -0.284538, 0.800307}, +{-0.44733, -0.367775, -0.815253}, +{-0.60098, -0.555968, -0.574215}, +{0.306122, -0.119952, -0.944405}, +{-0.189767, -0.981238, -0.0340667}, +{-0.338754, -0.930942, -0.136358}, +{-0.460797, 0.679859, 0.570488}, +{0.497971, -0.818553, 0.286351}, +{0.314076, 0.947068, -0.0664723}, +{-0.11104, 0.233948, 0.965887}, +{-0.979098, 0.101824, -0.176064}, +{0.12189, 0.92466, -0.360758}, +{-0.45518, 0.847691, 0.272456}, +{-0.621655, -0.689415, -0.371823}, +{0.0823722, 0.788344, -0.609695}, +{0.0613788, 0.581846, -0.81098}, +{0.663673, 0.00572015, -0.748001}, +{-0.776763, -0.628589, 0.038918}, +{0.560615, 0.196226, -0.804492}, +{0.537971, 0.712673, 0.450204}, +{-0.0863274, -0.831108, -0.54937}, +{0.0238151, 0.264817, -0.964005}, +{0.502303, 0.728614, -0.465632}, +{0.412646, -0.6123, -0.674397}, +{0.656336, 0.218424, 0.722159}, +{-0.654761, -0.605215, -0.452772}, +{-0.87275, -0.487838, -0.0179179}, +{-0.195067, 0.979401, 0.0521873}, +{0.346486, 0.808786, -0.475198}, +{0.429815, -0.896132, 0.110482}, +{-0.660316, -0.184504, 0.72797}, +{-0.724236, -0.154172, 0.672096}, +{-0.981784, 0.117084, 0.149641}, +{-0.0172067, 0.388196, 0.921416}, +{-0.769422, 0.634006, 0.077622}, +{-0.610489, -0.404893, -0.680709}, +{0.793901, 0.54637, -0.266835}, +{-0.575661, -0.593147, 0.562842}, +{0.700949, 0.71315, -0.0093296}, +{0.454319, 0.863401, 0.219391}, +{-0.00211086, -0.148559, 0.988901}, +{0.458001, -0.247984, 0.853662}, +{-0.842111, -0.478175, 0.249394}, +{-0.0251108, -0.694837, 0.718729}, +{0.119338, -0.929992, -0.347667}, +{-0.0229745, -0.912507, 0.408415}, +{-0.482469, -0.644423, 0.593247}, +{-0.452778, 0.817907, -0.354994}, +{-0.786412, -0.12755, 0.60439}, +{0.87464, 0.439447, -0.204674}, +{0.485918, 0.829483, 0.275394}, +{0.759681, -0.257372, -0.597198}, +{0.786305, -0.564795, -0.25046}, +{-0.247723, -0.675551, -0.694452}, +{0.482498, 0.163919, 0.860422}, +{-0.446129, -0.587375, -0.675248}, +{0.34874, -0.932196, 0.0969013}, +{0.411562, -0.18352, -0.892713}, +{-0.943731, -0.328216, 0.0405633}, +{0.0792567, 0.235715, 0.968585}, +{0.476319, -0.874296, 0.0934158}, +{-0.173073, -0.984575, 0.0256383}, +{0.521598, -0.665949, -0.533336}, +{-0.993085, -0.0492451, 0.106574}, +{-0.615467, 0.642767, 0.456126}, +{-0.12307, 0.967327, -0.22166}, +{-0.836658, 0.325703, 0.440365}, +{0.826517, 0.0274141, 0.562244}, +{-0.370971, -0.156905, 0.915293}, +{-0.190605, -0.84248, 0.503882}, +{0.423659, -0.131449, -0.896233}, +{-0.246442, -0.918876, 0.308112}, +{-0.436812, 0.563503, 0.701185}, +{-0.589822, 0.360203, -0.722747}, +{-0.150729, 0.971687, -0.181949}, +{0.182369, -0.259065, 0.948486}, +{0.565484, -0.824337, 0.0264015}, +{-0.288871, 0.837539, 0.463769}, +{-0.179551, 0.127951, -0.975392}, +{-0.7303, -0.232248, -0.642435}, +{-0.106135, 0.747645, 0.655563}, +{-0.815409, -0.307189, -0.490655}, +{-0.180687, -0.650094, -0.738059}, +{0.953687, -0.267219, 0.138109}, +{-0.574301, 0.77271, -0.270367}, +{0.170553, -0.629949, -0.757678}, +{-0.0443653, -0.698536, 0.714199}, +{0.752387, -0.609843, -0.249009}, +{-0.455843, -0.0791897, -0.886531}, +{0.0160565, 0.311812, 0.950008}, +{-0.167538, -0.309967, -0.935869}, +{0.357651, -0.331299, -0.873113}, +{-0.383063, -0.413339, 0.826083}, +{-0.133048, 0.267014, -0.954464}, +{0.753921, 0.628755, 0.190447}, +{0.978838, 0.187512, 0.0819446}, +{0.598604, 0.445371, 0.665822}, +{0.903263, -0.0863548, -0.420309}, +{0.640457, 0.189057, 0.74436}, +{0.29483, 0.21187, -0.931765}, +{0.919899, 0.128347, 0.370558}, +{0.918373, -0.294068, 0.264792}, +{-0.328636, 0.589816, 0.737642}, +{0.210884, 0.966106, -0.148889}, +{-0.440904, 0.822707, -0.358828}, +{-0.792197, 0.421927, -0.44091}, +{-0.0212758, -0.994748, -0.100121}, +{-0.501326, 0.186439, -0.844934}, +{-0.162997, -0.869702, 0.465887}, +{0.445067, -0.879534, 0.168331}, +{0.491205, -0.271261, 0.827729}, +{-0.755418, 0.208842, -0.621071}, +{-0.260741, -0.950779, -0.167431}, +{0.392083, 0.514629, -0.762514}, +{0.920366, 0.265734, 0.286901}, +{0.3935, -0.604221, 0.692874}, +{0.962161, -0.225074, -0.153585}, +{0.305594, -0.664454, -0.681992}, +{-0.0018586, 0.848349, -0.529435}, +{0.026884, 0.991318, 0.12871}, +{-0.0209917, 0.939844, -0.340957}, +{0.509332, 0.790323, -0.340546}, +{-0.26032, 0.455164, -0.851504}, +{0.22941, 0.866819, 0.442714}, +{-0.42561, -0.692639, 0.58233}, +{-0.369829, -0.849838, 0.375504}, +{0.102444, 0.146254, 0.983928}, +{-0.864415, 0.443831, -0.236221}, +{-0.363447, -0.427236, -0.827874}, +{0.498062, 0.311563, -0.809236}, +{0.0913729, 0.878917, 0.46814}, +{-0.332729, -0.937897, -0.0981852}, +{0.992008, -0.117579, 0.0457646}, +{-0.4764, 0.239668, -0.845933}, +{0.565042, 0.0310445, -0.824478}, +{-0.986771, 0.141368, 0.0793623}, +{-0.644377, 0.0889339, -0.759519}, +{0.92541, 0.378502, -0.0187607}, +{0.598127, 0.757229, 0.262391}, +{0.390511, -0.427914, -0.815101}, +{-0.787185, -0.101801, 0.608257}, +{-0.335048, -0.588411, -0.735878}, +{0.986674, 0.124934, -0.104236}, +{-0.5356, 0.54016, -0.649122}, +{-0.0470512, 0.723962, -0.688233}, +{-0.538927, 0.771496, 0.338158}, +{0.31158, -0.901519, 0.300304}, +{0.600644, 0.620407, -0.504303}, +{-0.34158, -0.534006, 0.773408}, +{0.430472, -0.532101, 0.729083}, +{0.232696, -0.0666136, -0.970266}, +{-0.171712, 0.298988, -0.93868}, +{0.546842, -0.257683, -0.796595}, +{0.32322, 0.668453, 0.66985}, +{0.845307, 0.431327, -0.315299}, +{0.717115, 0.308481, 0.624969}, +{-0.139106, -0.98823, -0.0636408}, +{-0.468382, -0.277982, 0.838657}, +{0.337845, -0.403722, 0.850217}, +{0.815311, -0.472265, 0.335013}, +{-0.678879, -0.656619, -0.328597}, +{-0.194051, 0.971232, -0.138028}, +{0.785586, -0.601124, 0.146644}, +{-0.230019, -0.630358, -0.741444}, +{-0.581121, 0.813817, 0.000576663}, +{-0.732469, 0.399189, 0.551487}, +{-0.853814, -0.0377942, 0.519204}, +{-0.997597, -0.0141114, 0.0678357}, +{0.816389, 0.180257, 0.54865}, +{-0.451547, -0.888382, 0.082964}, +{-0.677818, -0.709964, -0.191086}, +{-0.659039, -0.147187, -0.737566}, +{-0.535071, -0.835584, -0.124492}, +{0.243739, -0.114865, -0.963015}, +{-0.909375, -0.345083, -0.232281}, +{-0.644468, -0.556518, -0.524356}, +{-0.213441, -0.78817, -0.577262}, +{0.00536965, 0.906626, 0.4219}, +{-0.324016, 0.507467, 0.798431}, +{-0.762923, 0.626552, 0.159317}, +{0.88641, -0.248541, -0.390519}, +{0.586276, 0.0431029, 0.808964}, +{-0.593792, 0.0495092, -0.803094}, +{-0.225318, -0.316146, -0.921566}, +{0.660473, 0.223225, -0.7169}, +{-0.678025, -0.61397, -0.404132}, +{0.211066, -0.387393, -0.897428}, +{-0.0400299, 0.603656, -0.796239}, +{-0.613117, -0.0535484, -0.788175}, +{-0.797526, 0.0236241, -0.602821}, +{0.0487369, -0.858526, 0.510449}, +{-0.802202, 0.571132, -0.174013}, +{0.405569, -0.907003, -0.113398}, +{-0.286258, 0.0117484, 0.958081}, +{0.412843, -0.386642, 0.824663}, +{-0.0189412, 0.598376, 0.800991}, +{-0.629613, 0.47354, 0.615912}, +{0.568811, 0.182868, -0.801881}, +{-0.508431, 0.793644, -0.334107}, +{0.686108, -0.354731, -0.635155}, +{0.0163038, 0.566737, 0.823737}, +{0.880722, 0.282481, 0.380176}, +{-0.235871, -0.467574, -0.851903}, +{0.040288, -0.708124, 0.704938}, +{0.67195, -0.410241, 0.616592}, +{-0.499548, -0.43564, -0.748779}, +{-0.590898, -0.329174, -0.736535}, +{-0.815288, 0.575679, -0.0624431}, +{0.811606, -0.543352, -0.214625}, +{-0.786371, 0.0726878, 0.613463}, +{0.18561, 0.438509, 0.879351}, +{-0.60468, 0.125098, -0.786583}, +{0.65781, 0.409914, 0.631867}, +{-0.834536, 0.0237776, 0.550441}, +{0.512255, 0.835125, -0.200404}, +{-0.35771, 0.233291, -0.904223}, +{0.297328, 0.885598, 0.356808}, +{0.804627, -0.0847723, 0.587697}, +{-0.400933, -0.750469, -0.525405}, +{-0.209164, 0.191928, 0.958861}, +{-0.61364, -0.789067, -0.0286309}, +{-0.0941753, -0.919112, -0.382576}, +{-0.551848, 0.732281, -0.399034}, +{0.242897, 0.760733, 0.601902}, +{0.77338, -0.278636, -0.569425}, +{0.37735, -0.631143, 0.677691}, +{0.594082, 0.111438, 0.796648}, +{0.834667, 0.487633, 0.256018}, +{-0.131865, -0.138596, 0.981531}, +{0.40084, -0.681444, 0.612341}, +{-0.895388, 0.000361151, -0.445287}, +{0.356267, 0.316992, 0.878971}, +{0.994906, -0.0491068, -0.0880428}, +{-0.915295, -0.309169, -0.258167}, +{-0.346067, 0.883941, -0.314462}, +{-0.349293, 0.775755, 0.525547}, +{0.00638914, -0.997967, 0.0634046}, +{-0.411102, -0.90309, -0.124195}, +{-0.286838, -0.947916, 0.138486}, +{-0.992929, 0.111422, -0.0409538}, +{-0.768254, 0.212469, 0.603857}, +{0.0379018, 0.700003, -0.713134}, +{-0.373745, -0.924472, -0.075281}, +{-0.984779, -0.0922161, -0.147329}, +{-0.274121, 0.545258, -0.792181}, +{-0.142584, 0.464022, -0.874273}, +{0.22077, -0.516169, 0.827545}, +{-0.0423619, 0.353185, 0.934594}, +{-0.610409, -0.641137, 0.465128}, +{-0.776532, -0.577681, -0.25156}, +{0.471752, 0.855565, -0.213212}, +{0.00548194, -0.348559, 0.937271}, +{0.114943, 0.980386, 0.160098}, +{-0.43378, -0.901011, 0.00368596}, +{-0.197734, -0.536083, -0.820681}, +{-0.0866809, -0.123389, 0.988565}, +{0.337222, -0.915315, 0.220181}, +{0.807693, -0.0128885, -0.589462}, +{0.00730681, 0.911337, -0.411596}, +{-0.637418, -0.223376, -0.737429}, +{-0.150401, 0.349636, 0.924735}, +{-0.220253, -0.717422, -0.660904}, +{0.648102, 0.207573, -0.732719}, +{-0.425853, -0.886892, -0.179088}, +{-0.283374, 0.656596, 0.698985}, +{0.525362, -0.404039, -0.74883}, +{0.46541, -0.783305, -0.412101}, +{-0.437021, -0.547113, -0.713919}, +{0.384387, -0.888066, -0.252162}, +{-0.618659, -0.668758, 0.41234}, +{-0.774279, 0.342848, 0.531929}, +{-0.347088, 0.436636, -0.829987}, +{0.0303561, 0.85204, 0.522596}, +{0.0249281, -0.783841, 0.620462}, +{0.467256, -0.590633, -0.657894}, +{0.925161, 0.0136609, 0.37933}, +{0.392199, 0.467066, 0.792483}, +{-0.746546, 0.37082, -0.552415}, +{0.921672, 0.152588, -0.356703}, +{0.378443, 0.0253665, 0.925277}, +{0.574468, -0.802903, -0.159162}, +{0.0566599, -0.96409, -0.259463}, +{0.479905, -0.431077, -0.764109}, +{0.140711, -0.404712, -0.903553}, +{-0.291479, 0.941784, 0.167581}, +{-0.14342, -0.0459028, 0.988597}, +{0.215767, 0.866765, -0.449625}, +{0.0491895, -0.367383, -0.928768}, +{0.560423, -0.200899, 0.803471}, +{-0.430966, -0.563579, 0.704732}, +{-0.461726, 0.563289, 0.685212}, +{-0.453623, 0.615159, 0.64483}, +{0.789562, 0.109216, -0.603874}, +{-0.825065, -0.363472, -0.432615}, +{-0.896866, -0.430535, -0.101343}, +{-0.886107, 0.321064, 0.334265}, +{-0.747928, -0.62929, -0.211182}, +{0.203859, 0.97371, 0.101635}, +{0.92602, 0.364218, -0.0991587}, +{0.180679, -0.598835, 0.780225}, +{-0.248555, 0.910633, -0.330102}, +{-0.543452, 0.812075, -0.212587}, +{0.170156, 0.0396685, -0.984618}, +{0.39274, -0.357595, 0.847279}, +{0.52032, 0.294712, 0.801506}, +{0.341262, 0.908976, -0.239381}, +{0.270391, 0.953021, 0.136526}, +{-0.217978, 0.568213, 0.793486}, +{-0.412558, 0.073954, -0.907924}, +{0.307075, 0.927615, -0.212686}, +{-0.212229, 0.708936, 0.672583}, +{-0.45875, -0.815075, -0.353837}, +{0.160656, 0.907088, 0.389078}, +{-0.00419196, -0.821084, 0.570792}, +{-0.931442, 0.326992, -0.159665}, +{0.231185, -0.585436, 0.777057}, +{0.912655, 0.35412, 0.204107}, +{0.533371, -0.63685, 0.556721}, +{0.658644, 0.370608, 0.654857}, +{-0.874738, 0.451156, 0.176894}, +{0.215074, 0.462786, 0.859984}, +{0.272735, 0.954884, 0.117525}, +{0.539459, -0.828286, 0.151413}, +{-0.744302, 0.343906, -0.572489}, +{0.223486, -0.16372, -0.960859}, +{0.731851, 0.0120385, 0.681358}, +{-0.613769, 0.73608, 0.285437}, +{-0.711564, 0.698039, -0.0801108}, +{-0.450962, 0.758978, -0.469665}, +{-0.256135, -0.909277, 0.32804}, +{0.174452, 0.815542, -0.551777}, +{0.643331, 0.0034245, 0.765581}, +{0.0805686, 0.178396, 0.980655}, +{-0.0328732, 0.273511, 0.961307}, +{-0.278121, -0.532233, -0.799611}, +{-0.661443, 0.72171, 0.204028}, +{0.483429, -0.351741, -0.801608}, +{-0.489138, -0.416075, 0.766567}, +{0.750355, -0.60281, 0.271269}, +{-0.673634, 0.239467, -0.699195}, +{0.883011, -0.00583524, -0.469316}, +{-0.935313, 0.225378, 0.272753}, +{-0.440468, -0.866225, 0.235885}, +{-0.132566, -0.920511, 0.367541}, +{-0.520558, -0.677483, 0.51965}, +{-0.398604, 0.571019, -0.717672}, +{-0.348135, 0.911745, 0.217997}, +{-0.464791, 0.866315, -0.182942}, +{-0.309121, 0.643183, -0.700542}, +{0.138048, 0.98358, 0.116247}, +{-0.450234, 0.380369, -0.807842}, +{0.0682496, -0.741902, -0.667025}, +{0.180577, 0.960868, 0.210057}, +{0.868485, 0.41197, 0.275707}, +{0.428712, 0.843418, 0.323809}, +{-0.844003, -0.536222, 0.0111501}, +{-0.430226, -0.166097, -0.887309}, +{0.16515, 0.537304, -0.827061}, +{-0.441149, -0.324038, -0.836891}, +{-0.519058, -0.582707, 0.625325}, +{-0.0282464, 0.767153, 0.640842}, +{-0.435503, 0.308068, 0.845832}, +{0.76222, 0.624343, 0.17093}, +{0.517322, -0.42704, 0.74163}, +{0.385974, -0.279605, -0.879116}, +{0.620423, 0.746939, -0.239075}, +{-0.543439, 0.120607, 0.830739}, +{-0.771714, -0.279444, -0.571287}, +{0.182673, 0.871852, -0.454428}, +{-0.0161641, 0.310278, -0.950508}, +{0.384668, -0.132719, -0.913464}, +{-0.498985, -0.824184, 0.267833}, +{0.175717, 0.178695, 0.968086}, +{-0.641946, -0.664147, 0.383163}, +{0.551132, -0.828126, 0.102281}, +{-0.0699595, 0.0215918, -0.997316}, +{-0.912406, 0.401465, -0.0796312}, +{0.206297, -0.0317125, -0.977975}, +{-0.297909, 0.953548, 0.0446842}, +{0.818555, -0.473194, -0.325661}, +{0.314708, 0.00388889, 0.949181}, +{0.506442, -0.29813, -0.809096}, +{0.495412, -0.684703, 0.534555}, +{-0.336273, 0.540399, 0.771291}, +{-0.575167, -0.358276, 0.735406}, +{0.964002, 0.22519, -0.141386}, +{0.284677, 0.552276, 0.78355}, +{0.0353088, -0.135695, -0.990121}, +{-0.218563, 0.15554, 0.963347}, +{0.691999, 0.0712892, 0.718369}, +{0.237863, -0.926112, 0.292809}, +{-0.738214, -0.0234752, -0.674158}, +{0.112006, -0.814119, 0.569794}, +{0.54568, -0.0660479, -0.835387}, +{0.770386, 0.208305, 0.60259}, +{0.144564, 0.670373, -0.727806}, +{-0.638789, 0.244064, -0.729644}, +{-0.0557894, 0.956147, 0.287524}, +{0.18352, -0.802197, 0.568155}, +{0.214493, -0.295013, -0.931107}, +{-0.0935418, 0.804638, 0.586351}, +{-0.619843, 0.784678, -0.00862755}, +{0.742657, 0.146014, -0.653559}, +{0.20729, -0.693869, -0.68962}, +{-0.60053, 0.636934, 0.483403}, +{-0.0595036, -0.386012, 0.920573}, +{-0.832089, -0.388453, 0.395894}, +{0.624879, 0.773172, 0.108309}, +{0.320475, -0.947169, -0.0129107}, +{-0.136695, -0.594284, 0.792554}, +{0.195973, -0.301336, -0.933162}, +{0.451708, 0.791781, 0.411149}, +{0.478044, -0.544795, -0.688965}, +{-0.425127, 0.00518396, -0.905119}, +{0.796824, 0.554413, 0.240202}, +{0.591784, -0.32261, -0.738725}, +{0.148368, -0.724569, 0.673043}, +{0.493541, -0.589718, 0.639258}, +{-0.700782, -0.713372, -0.002346}, +{0.161862, -0.375768, 0.912469}, +{0.466338, 0.498572, -0.730722}, +{-0.95239, -0.142777, 0.269384}, +{0.280491, 0.780976, 0.558033}, +{-0.0644663, 0.480136, -0.874822}, +{-0.00557685, 0.995103, -0.0986872}, +{0.811447, 0.494228, -0.311918}, +{0.336124, 0.60497, 0.721825}, +{0.900026, 0.435184, 0.0238484}, +{-0.953769, 0.298459, -0.0353197}, +{0.238119, 0.178422, -0.954707}, +{-0.948766, 0.204587, 0.240807}, +{-0.497572, -0.483306, 0.720304}, +{-0.188148, 0.795118, -0.576531}, +{0.66834, -0.652834, -0.356551}, +{-0.0163034, 0.974261, 0.224831}, +{-0.566056, 0.814017, 0.130222}, +{0.820034, 0.535613, -0.201652}, +{0.528192, -0.113119, 0.841556}, +{-0.553001, 0.539679, 0.634773}, +{-0.686843, 0.412795, 0.598203}, +{-0.914808, 0.284273, -0.286905}, +{-0.487446, -0.807486, 0.332209}, +{-0.069919, -0.967037, -0.24485}, +{-0.993992, 0.00819683, -0.109146}, +{0.9108, 0.360851, 0.200574}, +{0.18496, -0.864617, -0.467148}, +{-0.446201, -0.4956, -0.745175}, +{-0.29924, 0.598129, -0.743436}, +{-0.196428, 0.83594, 0.512466}, +{0.29949, -0.455354, 0.838426}, +{-0.356353, 0.899429, 0.253062}, +{-0.00155663, 0.46074, -0.887534}, +{-0.176263, -0.981095, 0.079894}, +{0.0215919, 0.985301, 0.169459}, +{0.43701, 0.587931, 0.680705}, +{0.796599, 0.455765, -0.397126}, +{-0.108254, -0.803447, -0.585453}, +{-0.0768764, -0.650086, 0.755961}, +{0.386048, -0.565917, 0.728495}, +{0.382259, 0.176853, -0.906974}, +{-0.266249, -0.963846, 0.0106175}, +{0.98658, 0.157269, -0.0438794}, +{0.0149229, -0.991192, 0.13159}, +{-0.816669, -0.309479, 0.487108}, +{-0.79851, -0.586448, 0.13587}, +{0.216832, 0.76797, -0.602666}, +{0.89174, 0.0189059, -0.452153}, +{-0.328308, -0.763591, 0.556007}, +{0.83143, -0.536791, -0.143455}, +{0.441958, -0.730231, 0.520995}, +{-0.743334, -0.528105, -0.410559}, +{0.224172, -0.0985229, 0.969557}, +{-0.637509, 0.507607, -0.579584}, +{0.469148, 0.519398, 0.714232}, +{-0.319226, -0.877222, 0.358575}, +{0.898065, 0.417732, 0.137763}, +{-0.099507, -0.497502, 0.861737}, +{0.159573, -0.561332, -0.812061}, +{-0.52404, 0.0364752, 0.850912}, +{0.758107, 0.207642, 0.61819}, +{0.670427, -0.496657, -0.551235}, +{0.566635, -0.753554, 0.333289}, +{-0.771703, 0.595955, -0.222064}, +{-0.765602, 0.643141, -0.0149266}, +{-0.468694, 0.880902, -0.0658571}, +{0.702889, 0.707626, -0.0721965}, +{0.622179, -0.692399, 0.365345}, +{0.598067, -0.528263, 0.602705}, +{-0.226636, -0.938561, -0.260267}, +{0.786909, 0.261643, 0.558854}, +{0.872882, -0.196339, -0.446686}, +{0.0101144, 0.00820103, 0.999915}, +{-0.422104, 0.439229, 0.793036}, +{0.877469, -0.353106, -0.324598}, +{0.314481, 0.929084, 0.194691}, +{0.915907, -0.186664, 0.355347}, +{0.91686, 0.291534, -0.27272}, +{0.609159, 0.793034, -0.00473143}, +{-0.354846, 0.448743, 0.820191}, +{-0.858883, 0.49835, 0.118182}, +{0.495453, 0.662732, -0.561526}, +{0.673831, 0.570017, 0.470141}, +{-0.203014, 0.901112, -0.383123}, +{0.779737, 0.495931, -0.382181}, +{-0.476616, -0.557405, -0.679806}, +{0.885925, 0.424543, 0.186815}, +{0.181586, 0.439014, -0.87994}, +{-0.181881, 0.0754753, -0.98042}, +{0.862415, 0.38164, -0.332552}, +{-0.778778, -0.510925, 0.363951}, +{0.603291, -0.0925035, -0.792138}, +{0.288124, -0.87777, 0.38276}, +{0.790762, 0.27846, -0.54512}, +{-0.011042, -0.408191, -0.91283}, +{-0.294222, -0.698657, 0.652159}, +{0.405006, 0.43625, 0.803528}, +{-0.647582, -0.736003, 0.197325}, +{0.215671, -0.967785, -0.129914}, +{0.712878, 0.566434, 0.41347}, +{-0.0152908, -0.641976, 0.766572}, +{-0.652819, 0.669242, 0.354883}, +{-0.246104, 0.725451, -0.64277}, +{-0.520585, 0.59341, -0.613886}, +{0.554281, 0.747574, 0.365931}, +{-0.688654, -0.35872, -0.630139}, +{-0.25507, -0.836907, 0.48428}, +{0.486616, 0.727913, 0.48306}, +{-0.843267, -0.260083, -0.47038}, +{0.545478, 0.348476, -0.762246}, +{0.569854, 0.818421, -0.0738432}, +{0.750315, -0.196551, 0.631185}, +{-0.828083, 0.264037, -0.494533}, +{0.942376, 0.309682, 0.126585}, +{-0.576841, 0.214375, 0.788224}, +{0.754839, -0.169471, -0.633639}, +{0.630189, 0.15654, 0.760498}, +{-0.734806, 0.301914, 0.607379}, +{-0.569296, -0.742515, 0.352949}, +{0.675027, -0.51459, 0.528711}, +{-0.375712, 0.886361, 0.270564}, +{0.37575, 0.0813247, -0.923146}, +{-0.879208, -0.0780131, -0.470008}, +{0.741017, 0.282478, -0.60918}, +{-0.240742, 0.364346, -0.899608}, +{-0.996839, -0.064233, -0.0467549}, +{-0.0660192, 0.790336, -0.609106}, +{-0.868124, 0.495013, -0.0363628}, +{0.286388, 0.642647, -0.710624}, +{-0.474909, 0.240735, 0.846468}, +{0.0830907, -0.353883, 0.931592}, +{-0.625044, -0.473844, 0.620316}, +{0.369605, -0.715779, -0.592496}, +{0.818353, -0.0148031, -0.574525}, +{-0.638211, -0.103029, 0.762936}, +{0.255755, 0.964584, -0.0645492}, +{-0.867772, -0.107597, 0.485174}, +{-0.928284, -0.0284341, -0.370784}, +{-0.904595, 0.414683, 0.098718}, +{-0.944068, 0.174639, -0.279709}, +{-0.942562, -0.229695, -0.242522}, +{0.797428, 0.53142, 0.285835}, +{-0.388883, 0.918703, -0.0689605}, +{-0.73654, -0.0136207, -0.676256}, +{-0.401609, 0.76809, 0.498747}, +{-0.0151886, -0.899136, -0.437405}, +{0.287461, 0.896706, -0.336576}, +{0.267065, 0.291049, -0.918676}, +{0.00482882, -0.771766, -0.635888}, +{0.736981, -0.58476, -0.338991}, +{-0.44478, 0.0614279, 0.893531}, +{-0.184751, -0.660227, 0.727989}, +{-0.858813, -0.202172, 0.470709}, +{-0.957232, -0.00457735, -0.289285}, +{-0.620435, 0.301839, 0.723846}, +{-0.751373, -0.659871, 0.00301572}, +{-0.876374, -0.430388, -0.21618}, +{0.13295, -0.70929, 0.692266}, +{-0.95534, 0.123508, 0.26846}, +{0.903755, -0.421953, -0.0719832}, +{0.0307017, -0.776278, -0.629643}, +{0.830653, 0.532647, 0.162182}, +{0.1492, 0.988644, -0.0179815}, +{-0.261667, -0.441361, -0.85833}, +{0.651251, 0.54323, 0.52988}, +{0.584475, -0.0452954, -0.810146}, +{0.696754, 0.0371246, 0.716349}, +{0.522869, -0.081917, -0.848468}, +{-0.509665, -0.845898, 0.157159}, +{0.319676, -0.531589, 0.78436}, +{-0.725095, -0.436541, 0.532607}, +{-0.543242, -0.550909, -0.633552}, +{-0.92538, 0.16129, 0.343012}, +{-0.698421, -0.088602, 0.710181}, +{0.53057, 0.50733, -0.679053}, +{-0.264778, -0.415942, -0.869991}, +{0.480752, -0.169424, -0.860333}, +{0.280266, -0.948345, 0.148634}, +{-0.788316, 0.612687, 0.0563245}, +{-0.424913, 0.412778, 0.805645}, +{0.735899, 0.638879, -0.224246}, +{0.304264, 0.152315, -0.940332}, +{-0.105177, -0.346012, 0.932316}, +{-0.0933694, -0.981366, -0.167937}, +{-0.961452, 0.22582, -0.156894}, +{0.741651, 0.1078, 0.662067}, +{0.0272823, 0.96704, -0.253158}, +{-0.837409, -0.513202, -0.188067}, +{0.65311, -0.756904, 0.0233167}, +{0.130552, 0.987629, -0.0868651}, +{-0.548392, 0.700938, 0.456019}, +{-0.570945, -0.549975, 0.609549}, +{0.927601, -0.222953, -0.299747}, +{0.631683, -0.749062, 0.199709}, +{0.794033, -0.565579, 0.222784}, +{-0.985424, 0.16971, -0.0117232}, +{-0.987125, 0.158414, 0.0221294}, +{0.6945, -0.237093, -0.679305}, +{0.937707, 0.0419465, 0.344885}, +{-0.794767, -0.273678, -0.541706}, +{0.310837, -0.826207, 0.469854}, +{-0.0436197, -0.253575, 0.966332}, +{-0.643576, 0.762783, 0.0630263}, +{-0.602649, -0.79599, -0.0566936}, +{-0.720945, -0.665885, 0.191924}, +{-0.49762, 0.709801, -0.498555}, +{-0.779078, 0.230535, -0.583002}, +{-0.100661, -0.757263, 0.645307}, +{-0.178135, -0.754622, 0.631517}, +{-0.234843, 0.891708, 0.386918}, +{0.111796, -0.761882, -0.637994}, +{0.111543, -0.591351, -0.798663}, +{-0.913901, -0.281545, 0.292434}, +{0.0032347, -0.938902, 0.344168}, +{0.648865, 0.754799, -0.0961935}, +{0.362354, 0.146616, -0.920437}, +{0.594847, -0.802784, 0.041174}, +{0.685815, -0.700193, 0.198463}, +{-0.274087, 0.920141, 0.279673}, +{0.986729, -0.0918803, -0.133883}, +{-0.663129, 0.562876, 0.493388}, +{0.690926, -0.675357, 0.257906}, +{-0.211755, -0.951508, 0.223142}, +{0.129467, -0.727634, -0.673637}, +{0.14455, -0.553306, 0.82034}, +{0.685535, 0.514939, -0.514664}, +{0.0194242, 0.664541, 0.746999}, +{0.972224, -0.162932, -0.168031}, +{0.889432, -0.273187, 0.366443}, +{0.473777, 0.879829, -0.0378938}, +{-0.683078, 0.0586597, -0.727986}, +{-0.95912, 0.0867863, 0.269365}, +{0.532463, 0.465156, 0.707187}, +{0.718662, -0.280869, -0.636111}, +{-0.552725, 0.591829, 0.586715}, +{-0.0558193, -0.351468, -0.934534}, +{0.559229, -0.685984, 0.465499}, +{0.0948403, 0.424455, 0.900468}, +{0.757652, -0.223044, 0.613363}, +{-0.920629, -0.169817, 0.351575}, +{0.0710956, -0.994631, -0.0752017}, +{-0.455387, 0.626499, -0.632552}, +{0.322906, -0.0925992, 0.94189}, +{-0.974208, 0.182864, 0.132209}, +{0.350204, 0.712835, -0.607638}, +{0.334015, 0.884989, -0.324389}, +{0.358948, -0.876681, -0.320291}, +{0.657462, 0.205966, -0.724791}, +{-0.706019, -0.0659589, 0.705114}, +{0.859072, -0.3472, -0.376095}, +{0.940719, 0.283011, -0.186958}, +{-0.85364, -0.224256, -0.470115}, +{0.949426, 0.220744, 0.2233}, +{0.783962, -0.617441, -0.0645775}, +{-0.975956, 0.21251, 0.0484636}, +{0.775438, 0.631366, 0.00849196}, +{0.39182, -0.0622683, -0.917932}, +{-0.059789, 0.118599, 0.99114}, +{-0.360291, -0.245463, -0.899965}, +{-0.601481, 0.676923, 0.424259}, +{-0.20259, -0.913009, -0.354078}, +{0.459564, -0.588781, 0.664934}, +{0.230215, 0.493284, -0.838852}, +{-0.343635, -0.735311, -0.584151}, +{-0.409754, 0.163919, -0.897347}, +{0.126217, -0.821199, -0.556508}, +{-0.247265, 0.936415, -0.248972}, +{-0.316676, -0.789865, -0.525194}, +{-0.107463, 0.657995, 0.745315}, +{-0.355742, 0.564518, 0.744826}, +{-0.543011, 0.828142, -0.138994}, +{0.12793, -0.879784, 0.457837}, +{-0.991698, -0.0942452, -0.0874776}, +{-0.425363, -0.632764, -0.647051}, +{0.213576, 0.562641, -0.798637}, +{0.99898, -0.00806954, 0.0444214}, +{-0.504464, -0.193335, 0.841509}, +{0.0591607, -0.0125521, -0.99817}, +{0.865485, -0.158699, -0.475132}, +{0.119332, -0.988565, -0.09219}, +{-0.477049, -0.208453, 0.853798}, +{-0.418463, -0.902417, -0.102627}, +{-0.891838, 0.452298, 0.00724576}, +{0.616866, 0.413522, -0.669684}, +{0.634868, -0.372626, 0.676825}, +{0.690414, 0.663074, 0.289243}, +{-0.444446, 0.170766, -0.879378}, +{-0.67984, -0.143921, 0.719099}, +{-0.163758, -0.236728, -0.957676}, +{0.704144, -0.118334, -0.700127}, +{0.736601, -0.672842, 0.0685784}, +{0.0709733, 0.182407, -0.980658}, +{-0.120307, -0.13727, -0.9832}, +{-0.639445, 0.759403, 0.120072}, +{-0.525032, 0.805427, 0.275008}, +{0.891131, -0.424638, 0.1599}, +{-0.84899, 0.523378, -0.0727353}, +{-0.824555, -0.565611, 0.0139102}, +{-0.850265, 0.0181804, -0.52604}, +{0.256361, 0.564212, 0.784821}, +{-0.800344, 0.11303, -0.588789}, +{0.0234081, 0.987169, -0.157953}, +{-0.121714, -0.248635, 0.96092}, +{-0.178905, 0.889316, 0.420844}, +{-0.358664, -0.504496, -0.785394}, +{-0.470362, -0.0791909, 0.878913}, +{0.30349, 0.841588, -0.446792}, +{0.933447, -0.288374, 0.213349}, +{0.816841, -0.405678, -0.410117}, +{0.830222, 0.551839, 0.0787719}, +{-0.613068, -0.782653, 0.107713}, +{0.865561, -0.444805, -0.230114}, +{-0.662471, -0.60741, 0.43839}, +{0.0326518, -0.877506, -0.478453}, +{0.464731, 0.643192, 0.608547}, +{-0.944889, 0.224951, -0.237869}, +{-0.302373, 0.0874119, 0.949173}, +{-0.0462551, -0.1713, 0.984133}, +{0.358364, -0.208635, -0.90997}, +{-0.157454, 0.0409117, -0.986678}, +{0.249773, -0.0524162, 0.966885}, +{0.321835, -0.859658, -0.396749}, +{0.4327, -0.694337, -0.575036}, +{-0.569399, 0.819209, 0.0684162}, +{0.0904767, -0.238756, -0.966855}, +{0.869929, 0.382537, -0.31127}, +{0.733488, 0.505524, 0.454358}, +{-0.855304, -0.396773, -0.333207}, +{0.45836, 0.136808, -0.878174}, +{0.298194, -0.909608, 0.289297}, +{-0.988645, -0.148851, 0.0206243}, +{-0.117944, -0.983034, 0.140478}, +{0.602534, -0.664813, 0.441562}, +{0.671508, 0.526886, -0.521026}, +{-0.167556, -0.778759, 0.604533}, +{0.455937, 0.169028, -0.873814}, +{0.991583, 0.115411, -0.0586829}, +{-0.00861939, -0.547692, -0.836636}, +{0.812841, 0.545421, -0.204465}, +{-0.908553, 0.259581, 0.327335}, +{-0.433997, -0.307657, 0.846755}, +{0.919116, 0.0399101, -0.391959}, +{-0.725259, 0.413168, -0.550719}, +{-0.705231, -0.597492, 0.381644}, +{-0.219463, -0.0440314, -0.974627}, +{0.607282, -0.431423, -0.667145}, +{-0.174042, -0.0374028, -0.984028}, +{0.66356, -0.454256, 0.594424}, +{-0.484671, 0.71054, 0.510125}, +{0.93613, 0.10453, 0.335759}, +{0.671025, 0.374079, -0.640148}, +{0.54611, -0.712642, 0.440348}, +{0.207995, -0.791596, -0.574556}, +{0.835454, 0.364755, -0.41106}, +{-0.206201, 0.416886, -0.885261}, +{0.34985, 0.617049, 0.70488}, +{0.384801, 0.0535241, -0.921446}, +{0.262527, -0.483425, 0.835093}, +{-0.733786, -0.479209, -0.481577}, +{0.326935, 0.810971, 0.485222}, +{0.966934, 0.0404131, -0.251805}, +{-0.310243, 0.184171, -0.932647}, +{0.139967, -0.679728, -0.719985}, +{0.443626, -0.816249, 0.370045}, +{-0.359235, 0.0334447, -0.932648}, +{0.169891, -0.49263, 0.853494}, +{0.578096, 0.26258, 0.772565}, +{0.0537304, 0.521124, 0.851788}, +{-0.717517, 0.451477, 0.530413}, +{0.763117, -0.470906, 0.442607}, +{0.91703, 0.0926498, 0.387907}, +{0.316176, -0.772983, -0.550027}, +{-0.90843, -0.052426, -0.414737}, +{-0.347493, -0.58359, 0.733942}, +{-0.0626683, -0.882924, -0.465315}, +{-0.252387, -0.548609, -0.797075}, +{0.714173, -0.0914793, 0.693965}, +{0.233782, -0.767252, 0.597219}, +{-0.314474, -0.925876, 0.209426}, +{0.931926, -0.10596, 0.346824}, +{-0.778225, 0.627037, -0.0344964}, +{0.275732, -0.325215, 0.904548}, +{0.559885, -0.320851, 0.763927}, +{0.901886, 0.425669, 0.073535}, +{0.176656, -0.977845, 0.112304}, +{-0.653968, -0.755363, -0.0418683}, +{-0.429666, 0.876905, -0.215466}, +{-0.542134, -0.40844, -0.734348}, +{-0.823489, -0.168363, -0.541775}, +{-0.729667, -0.0944553, 0.677248}, +{-0.548068, 0.830967, -0.0954776}, +{0.297922, 0.881448, 0.366457}, +{0.788219, -0.0982938, -0.607494}, +{-0.0417261, 0.908809, 0.41512}, +{0.000459814, -0.57457, -0.818456}, +{0.538454, -0.755001, -0.374221}, +{0.19097, 0.0742313, -0.978785}, +{-0.147496, -0.106212, -0.983343}, +{0.678169, 0.444063, -0.585573}, +{0.133644, -0.373224, -0.918065}, +{0.585554, 0.502003, -0.636491}, +{-0.870876, -0.384724, -0.30588}, +{-0.153442, 0.800151, 0.57984}, +{0.0315088, -0.929841, -0.366609}, +{0.8231, -0.527345, -0.210746}, +{-0.309131, 0.730474, -0.608971}, +{0.651242, 0.758544, -0.0222261}, +{-0.600754, -0.125364, 0.789543}, +{-0.814695, 0.533416, 0.227463}, +{0.0515687, -0.719852, 0.692209}, +{-0.283638, -0.205937, 0.936557}, +{0.815047, 0.10128, -0.570474}, +{0.191576, -0.52085, -0.831873}, +{-0.87927, -0.472758, 0.0581659}, +{0.400315, -0.85878, 0.319758}, +{0.531489, 0.666551, -0.522714}, +{-0.503905, -0.599228, -0.622098}, +{-0.979557, -0.175644, 0.098065}, +{0.594485, 0.734085, -0.328187}, +{-0.241374, 0.838442, -0.488625}, +{0.183644, 0.955114, 0.232449}, +{0.274368, 0.214868, -0.937312}, +{0.499199, -0.780444, 0.37644}, +{-0.79078, 0.518353, 0.325542}, +{-0.216024, 0.970045, -0.111113}, +{0.819198, 0.572818, -0.028193}, +{0.243772, -0.961188, 0.129201}, +{-0.424141, 0.00116128, -0.905595}, +{0.209246, 0.0705106, -0.975318}, +{-0.17841, 0.0112228, 0.983892}, +{-0.640541, 0.696178, 0.324104}, +{0.793991, 0.570645, 0.209625}, +{-0.677619, -0.0775114, -0.731317}, +{-0.302385, 0.00218443, -0.953183}, +{0.624764, -0.769869, -0.130275}, +{0.879075, 0.0869285, -0.46869}, +{-0.975673, 0.01647, -0.218613}, +{0.712043, 0.263357, -0.650875}, +{0.0300125, 0.973625, -0.226174}, +{0.192627, -0.828075, 0.526485}, +{-0.839596, -0.460247, -0.288533}, +{-0.6383, 0.189992, -0.745973}, +{0.845088, 0.137383, 0.516674}, +{0.653089, 0.718293, 0.239853}, +{0.155554, -0.708725, -0.688122}, +{-0.264104, -0.491577, 0.82982}, +{0.240888, -0.930408, 0.276251}, +{-0.983133, -0.00417462, 0.182844}, +{0.821787, -0.56598, -0.0658178}, +{0.489345, -0.814524, -0.311596}, +{-0.389881, -0.87237, 0.294896}, +{-0.919132, -0.138291, 0.368879}, +{0.972876, -0.0365673, -0.22842}, +{0.175514, 0.253549, -0.951266}, +{-0.0238983, -0.503645, 0.86358}, +{0.0398349, 0.00378197, -0.999199}, +{-0.51868, -0.345269, 0.782151}, +{-0.501547, -0.210732, -0.839073}, +{-0.0776411, -0.940979, 0.32944}, +{0.956986, 0.270351, -0.105299}, +{-0.56079, 0.364754, 0.743283}, +{-0.940409, 0.318648, -0.118722}, +{-0.150701, -0.436316, 0.887084}, +{0.915301, 0.402326, -0.0189012}, +{0.474153, 0.664444, -0.577662}, +{-0.0937675, -0.739393, -0.666713}, +{0.950082, 0.311892, -0.00818979}, +{0.949767, 0.0653237, -0.306063}, +{-0.530291, -0.323287, -0.783758}, +{-0.076617, -0.826303, 0.557991}, +{-0.0351631, 0.862663, 0.504555}, +{0.756335, 0.266598, -0.597396}, +{-0.620935, 0.00582877, 0.783841}, +{0.99931, -0.00239347, -0.0370586}, +{-0.621095, -0.288003, -0.7289}, +{0.764099, 0.319585, -0.560373}, +{-0.257949, -0.389364, 0.884227}, +{0.346314, -0.596919, -0.723709}, +{0.647905, 0.629267, -0.429235}, +{0.656249, 0.726568, 0.203561}, +{-0.980253, -0.114104, -0.16151}, +{0.164778, -0.47099, 0.866612}, +{0.977933, -0.0602176, 0.200053}, +{0.191464, 0.3648, -0.911187}, +{-0.49252, 0.704265, -0.511307}, +{-0.850865, -0.508114, -0.133598}, +{0.246271, -0.668264, 0.701979}, +{-0.763972, -0.359764, 0.535645}, +{-0.435154, 0.50026, 0.748586}, +{0.985818, 0.0265021, 0.165712}, +{0.870094, 0.303012, -0.388741}, +{-0.251646, -0.596859, 0.761862}, +{-0.0314153, 0.972191, -0.232074}, +{-0.635522, -0.295725, 0.713203}, +{-0.284228, -0.924444, 0.254199}, +{0.256031, -0.147252, -0.955387}, +{0.243343, -0.512303, -0.823608}, +{-0.677562, -0.711408, -0.186569}, +{-0.91637, 0.191876, 0.351355}, +{0.599753, 0.433165, -0.672804}, +{0.750807, -0.33431, -0.569672}, +{-0.778404, 0.436582, 0.451091}, +{-0.106975, 0.0191631, -0.994077}, +{-0.527226, -0.0978258, -0.844075}, +{-0.210132, -0.592649, -0.777568}, +{0.833936, -0.339995, -0.434689}, +{0.529203, -0.544977, 0.650341}, +{0.466094, -0.31717, 0.82593}, +{0.598173, -0.242013, 0.763949}, +{0.261827, 0.3735, -0.889913}, +{0.539218, 0.44177, -0.716996}, +{0.562764, -0.581429, -0.587569}, +{0.332937, -0.837738, 0.432837}, +{-0.466274, 0.820268, 0.331283}, +{0.603179, -0.439103, 0.665856}, +{-0.627887, 0.507965, 0.589685}, +{0.0601872, 0.909071, 0.412271}, +{-0.955793, 0.0497691, -0.289799}, +{0.587744, -0.693149, -0.417254}, +{-0.0415099, -0.72393, -0.688624}, +{0.287112, -0.212077, 0.934125}, +{-0.0483611, 0.460892, 0.886138}, +{-0.148704, 0.965334, -0.214515}, +{-0.874474, -0.469137, 0.123309}, +{-0.990191, -0.126203, 0.059953}, +{0.817607, -0.322577, 0.476931}, +{0.274553, 0.958455, -0.0773603}, +{-0.134682, 0.688232, 0.71288}, +{-0.773644, 0.623428, -0.11319}, +{-5.89496e-05, 0.741657, 0.670779}, +{0.394616, -0.328739, -0.858027}, +{0.786066, 0.46631, 0.405777}, +{-0.655556, -0.359965, 0.663831}, +{0.171929, 0.659698, -0.7316}, +{0.299308, 0.0269345, -0.953776}, +{0.998258, 0.0209519, 0.0551481}, +{0.936859, 0.249259, 0.245286}, +{0.765375, -0.596536, 0.241548}, +{-0.0613229, 0.824879, -0.561974}, +{-0.645998, -0.464598, -0.60567}, +{-0.0972915, 0.873121, 0.477696}, +{0.885497, 0.353956, 0.301016}, +{-0.145704, 0.93337, 0.328009}, +{0.385144, -0.466525, -0.796253}, +{0.659568, 0.269859, 0.701531}, +{-0.694602, 0.281866, 0.661876}, +{0.668549, -0.262047, -0.695969}, +{-0.620818, -0.457639, 0.636515}, +{0.511494, 0.350507, 0.78455}, +{0.461604, -0.640301, -0.613952}, +{0.375621, 0.409852, 0.831222}, +{0.820899, -0.139756, -0.553709}, +{0.019905, 0.99799, -0.0601682}, +{-0.709974, -0.180407, -0.680728}, +{0.740996, -0.649002, -0.1724}, +{0.69036, 0.000954807, -0.723465}, +{0.404682, -0.879801, 0.249363}, +{0.748239, -0.0815755, 0.658395}, +{0.671325, -0.0221961, 0.74083}, +{0.124601, 0.770172, -0.625547}, +{-0.118001, 0.339902, -0.933029}, +{0.168877, -0.719702, 0.673431}, +{0.554339, -0.706647, -0.439726}, +{-0.791062, 0.585748, 0.176409}, +{0.318047, -0.38521, 0.866291}, +{0.431697, 0.0799711, 0.898467}, +{0.369864, -0.568824, -0.734602}, +{0.215689, 0.938682, 0.268989}, +{-0.352946, 0.81691, 0.456167}, +{0.60321, -0.448085, 0.659816}, +{-0.425225, 0.904675, -0.0273187}, +{-0.654337, -0.413613, -0.633063}, +{0.0782959, 0.624162, -0.777362}, +{-0.418223, -0.588036, 0.692317}, +{-0.937826, -0.34017, -0.0690427}, +{-0.204592, 0.0089079, -0.978807}, +{0.473523, 0.0721667, 0.87782}, +{-0.645711, 0.544814, 0.53501}, +{0.383744, 0.854524, -0.350043}, +{0.366792, 0.805391, 0.465627}, +{0.838413, -0.543225, 0.0443802}, +{-0.733075, 0.672997, 0.0983661}, +{-0.514796, 0.158357, 0.842561}, +{-0.72453, 0.575178, 0.379772}, +{0.411343, -0.18143, 0.893241}, +{0.173458, 0.604936, 0.777152}, +{-0.920168, 0.376051, -0.108981}, +{0.105474, -0.993208, 0.0491193}, +{0.53469, -0.694905, 0.480847}, +{-0.231997, -0.962862, -0.138106}, +{-0.821562, 0.504169, 0.266178}, +{0.421044, -0.656596, 0.625783}, +{0.843756, -0.53382, -0.0557783}, +{-0.433606, 0.850786, -0.296899}, +{0.00372182, -0.642433, -0.766333}, +{0.412864, -0.806905, 0.422431}, +{0.123034, -0.48801, 0.864123}, +{-0.443562, -0.446588, 0.777053}, +{-0.819389, 0.209904, -0.533425}, +{0.111726, 0.156798, -0.981291}, +{0.214009, -0.712208, -0.66855}, +{0.149246, -0.464184, -0.873074}, +{0.60412, -0.638835, -0.476372}, +{-0.747845, -0.601204, 0.28157}, +{-0.65969, -0.343959, -0.668208}, +{-0.722156, -0.598036, -0.347625}, +{-0.672702, -0.624359, 0.397049}, +{-0.658922, -0.54558, -0.517846}, +{-0.826152, 0.465378, -0.317641}, +{-0.282905, 0.210378, 0.935792}, +{-0.156639, 0.850159, 0.502687}, +{0.355161, -0.934804, -0.00171247}, +{-0.502822, 0.550751, 0.666215}, +{0.916949, -0.388307, -0.091773}, +{-0.99333, 0.0671763, -0.0937125}, +{-0.457716, 0.677717, 0.575495}, +{-0.173418, 0.886306, -0.429405}, +{-0.344385, -0.0278936, -0.938414}, +{-0.262297, 0.0436831, -0.963998}, +{0.987115, -0.159831, 0.0075656}, +{-0.180186, 0.446505, 0.876451}, +{0.313629, 0.72764, 0.610063}, +{-0.0423349, -0.109252, -0.993112}, +{-0.95274, 0.261592, 0.154457}, +{0.925288, -0.0292937, -0.378131}, +{0.474369, -0.868437, 0.144194}, +{0.689195, 0.160826, -0.706502}, +{0.125528, 0.110244, 0.985946}, +{0.0231181, -0.945445, -0.324962}, +{0.20662, 0.815494, 0.540628}, +{-0.219843, -0.896669, -0.384258}, +{-0.815297, 0.458069, 0.354209}, +{0.168339, 0.421492, -0.891071}, +{0.585666, 0.202557, 0.784835}, +{0.310514, -0.638222, 0.704452}, +{-0.812265, -0.222522, 0.539176}, +{-0.495839, 0.561038, -0.662857}, +{-0.301373, 0.908805, -0.288526}, +{0.754017, 0.646932, 0.113746}, +{-0.508164, -0.751853, -0.420103}, +{0.726083, 0.645145, 0.237891}, +{0.28512, 0.731719, -0.619107}, +{0.0794979, 0.984845, -0.154147}, +{0.0556937, 0.605824, 0.793647}, +{-0.233296, -0.968743, -0.0843156}, +{0.333942, 0.490241, -0.805075}, +{-0.079861, -0.851614, 0.51805}, +{-0.970856, 0.227645, -0.0749425}, +{-0.459637, 0.457689, 0.761088}, +{-0.978842, 0.190446, -0.0748231}, +{0.650879, 0.33597, -0.680794}, +{0.801608, -0.59763, -0.0162114}, +{-0.629888, 0.389155, 0.672161}, +{-0.856618, 0.450623, 0.251285}, +{0.171063, -0.938621, -0.299548}, +{-0.567498, 0.590647, 0.573657}, +{0.266643, 0.769541, -0.580265}, +{-0.251205, -0.836471, -0.487044}, +{-0.306067, 0.52277, 0.795635}, +{0.430566, -0.696437, -0.574098}, +{-0.425686, 0.148607, 0.892585}, +{-0.467647, -0.883912, 0.00228919}, +{-0.302421, -0.840909, -0.448792}, +{0.464827, 0.404151, 0.787781}, +{-0.857466, -0.183054, -0.480878}, +{0.915171, -0.190973, -0.354953}, +{0.158798, -0.566308, 0.808751}, +{0.443843, -0.884444, -0.144092}, +{0.346685, -0.455329, -0.820052}, +{0.272133, 0.626757, 0.73015}, +{-0.872292, 0.16362, -0.460799}, +{-0.984147, -0.176851, -0.0133781}, +{-0.891848, -0.215392, 0.397761}, +{0.264707, 0.205057, 0.942275}, +{0.64309, -0.679922, -0.352337}, +{0.812084, 0.0371636, 0.582356}, +{0.701293, -0.663248, -0.261325}, +{-0.338258, 0.844877, 0.414445}, +{-0.674509, 0.369935, -0.638893}, +{-0.197091, -0.968383, 0.152937}, +{-0.667405, 0.638121, -0.383891}, +{0.233795, -0.398034, 0.887079}, +{-0.939548, -0.301691, 0.16196}, +{0.160858, -0.237079, -0.958081}, +{0.0845287, 0.8662, -0.492496}, +{-0.0435361, -0.998979, -0.0120507}, +{0.989118, -0.109914, 0.0977977}, +{-0.127403, -0.75228, 0.646408}, +{0.234517, 0.930542, -0.281234}, +{-0.999808, -0.0128499, -0.014774}, +{-0.836485, 0.220187, -0.501808}, +{-0.50873, 0.158151, 0.846275}, +{-0.944849, 0.268477, 0.187567}, +{0.930731, -0.292601, -0.21937}, +{-0.414724, 0.339664, 0.844176}, +{0.373422, -0.633236, -0.677915}, +{-0.323997, -0.811983, -0.485499}, +{-0.0390545, 0.50504, 0.862212}, +{0.539234, 0.776146, 0.32684}, +{-0.704434, -0.217529, 0.675614}, +{-0.368794, 0.346818, 0.862385}, +{0.731713, -0.553445, 0.397863}, +{0.677381, 0.662923, -0.318886}, +{0.301013, -0.202372, -0.9319}, +{-0.212346, -0.230559, 0.949606}, +{0.213772, -0.503309, 0.837247}, +{-0.591128, -0.244878, -0.768507}, +{0.309146, -0.881582, -0.35671}, +{-0.620206, -0.55891, 0.550422}, +{0.451951, -0.889024, 0.0733245}, +{-0.579982, -0.490442, 0.650452}, +{0.148781, 0.705572, 0.692844}, +{-0.334492, 0.76468, 0.550799}, +{0.756816, 0.153657, 0.63531}, +{0.0222091, -0.89284, 0.449826}, +{0.090129, 0.828677, 0.552423}, +{0.238396, -0.95839, -0.15702}, +{0.753027, 0.345981, 0.559685}, +{0.684964, 0.557478, -0.469088}, +{0.692084, -0.658389, -0.295879}, +{-0.351483, -0.255571, 0.900635}, +{-0.0671527, 0.997507, 0.0216648}, +{-0.393364, -0.899929, 0.188129}, +{0.406961, 0.837373, -0.36495}, +{0.708903, 0.00497088, -0.705289}, +{-0.50015, 0.505166, -0.703319}, +{-0.51242, -0.618309, 0.595919}, +{0.950543, 0.143783, 0.275308}, +{0.983846, 0.074267, -0.162887}, +{-0.56831, 0.570832, 0.5926}, +{0.234092, -0.863076, -0.447551}, +{-0.381405, 0.27316, 0.883127}, +{0.95169, -0.297611, 0.0755842}, +{-0.712931, 0.65196, -0.25822}, +{-0.899104, 0.0366451, -0.436198}, +{0.911486, -0.11193, 0.39581}, +{-0.0022299, 0.814253, 0.580506}, +{-0.839844, 0.50398, -0.201658}, +{0.444181, 0.49455, 0.747077}, +{0.83608, -0.396375, -0.379285}, +{0.134471, 0.880986, 0.453631}, +{0.213357, 0.644758, -0.734006}, +{0.426643, 0.654371, -0.624319}, +{-0.635237, 0.348319, 0.68931}, +{-0.408673, 0.6227, 0.667256}, +{0.447635, 0.182926, 0.875306}, +{-0.510009, -0.259357, -0.820137}, +{-0.368033, 0.68987, 0.623404}, +{-0.527456, -0.777019, 0.343556}, +{-0.926919, 0.146508, -0.34548}, +{0.261801, -0.90481, 0.335827}, +{0.773176, -0.634163, 0.00604129}, +{0.155679, -0.14153, -0.977616}, +{0.332736, 0.512715, 0.791461}, +{0.495279, 0.781618, -0.379173}, +{0.625207, 0.777772, 0.0647083}, +{-0.236341, 0.969085, -0.0708258}, +{0.216341, 0.48675, 0.846328}, +{0.561434, -0.82746, -0.0101344}, +{-0.452723, 0.565684, -0.689234}, +{0.513916, 0.750113, -0.416198}, +{0.0347479, -0.0683969, -0.997053}, +{-0.944211, -0.188021, 0.270394}, +{0.886875, -0.460895, 0.0320881}, +{-0.584536, -0.724451, -0.365362}, +{0.628258, -0.66215, -0.408472}, +{0.76081, 0.379045, 0.526776}, +{-0.153342, -0.670396, -0.725986}, +{-0.0114366, -0.565435, 0.824713}, +{-0.938396, 0.331407, 0.0978935}, +{0.795728, -0.596888, 0.102671}, +{0.838039, -0.372632, 0.398542}, +{-0.561378, -0.764354, -0.317203}, +{0.676486, 0.479759, 0.558747}, +{0.557947, 0.689209, -0.462261}, +{-0.265924, 0.593949, 0.759282}, +{-0.799428, -0.581931, 0.149237}, +{0.45893, -0.305148, 0.834427}, +{0.639561, -0.638439, 0.428201}, +{0.904679, -0.138703, -0.402886}, +{-0.938455, 0.0643892, -0.339346}, +{-0.683107, -0.646823, 0.339095}, +{0.203632, 0.920944, -0.332259}, +{-0.240509, -0.819002, 0.520952}, +{-0.821371, -0.504624, -0.265902}, +{0.336362, -0.678416, 0.653156}, +{0.955741, -0.276059, -0.101739}, +{0.196924, -0.00279708, -0.980415}, +{0.39665, 0.741477, -0.541185}, +{-0.449283, -0.0169401, 0.893229}, +{0.863886, 0.478711, -0.15664}, +{-0.0357932, 0.351303, -0.935577}, +{-0.125839, -0.948808, 0.289702}, +{-0.146262, -0.989221, 0.00705224}, +{0.79852, -0.536935, -0.272153}, +{-0.356328, 0.927727, -0.111145}, +{-0.906019, -0.250389, 0.341227}, +{-0.803486, -0.0525895, 0.592996}, +{0.901219, 0.260754, 0.346137}, +{-0.243058, -0.503953, -0.828827}, +{-0.198521, -0.501043, 0.842345}, +{-0.796423, 0.56039, -0.227318}, +{-0.21248, 0.925426, 0.313751}, +{-0.0390276, 0.539506, -0.841077}, +{-0.737398, -0.631663, 0.239263}, +{-0.597631, 0.77358, -0.21074}, +{0.740756, 0.662556, -0.110905}, +{0.578925, 0.527461, 0.621796}, +{0.0363777, -0.999164, -0.018655}, +{-0.460731, -0.143626, -0.875841}, +{-0.925166, 0.378241, 0.0316327}, +{-0.419056, -0.768592, -0.483383}, +{0.26347, -0.221034, 0.939003}, +{-0.544266, -0.574771, 0.611075}, +{-0.746663, -0.427935, -0.50928}, +{-0.0619359, 0.26529, 0.962177}, +{0.021649, -0.919931, -0.391482}, +{-0.87139, 0.0706917, -0.485471}, +{0.258661, -0.716464, -0.6479}, +{0.0288779, -0.901951, 0.430871}, +{-0.41798, -0.827503, 0.374875}, +{0.442131, -0.276525, -0.853261}, +{0.149144, 0.184364, 0.971476}, +{0.750061, -0.552953, -0.362838}, +{0.711364, 0.645471, -0.278079}, +{-0.693798, 0.704041, -0.151558}, +{0.233167, -0.822244, 0.51918}, +{-0.209169, 0.0646803, 0.975738}, +{0.750489, -0.483675, 0.450362}, +{-0.674274, 0.71865, -0.169994}, +{-0.553937, -0.825238, 0.110163}, +{-0.119726, 0.691, -0.712871}, +{-0.82293, -0.547466, -0.151877}, +{0.78167, -0.530225, 0.328411}, +{-0.822027, -0.561008, -0.0976778}, +{0.974516, 0.220653, -0.040392}, +{-0.92558, -0.043001, -0.376102}, +{0.849315, 0.475768, -0.228711}, +{-0.728196, 0.0892794, -0.679529}, +{-0.0686843, -0.0592949, 0.995875}, +{-0.624542, 0.779693, 0.0450231}, +{-0.553125, -0.668476, 0.497185}, +{0.69708, 0.469772, -0.541658}, +{0.085354, -0.706237, -0.702812}, +{0.308169, -0.622133, 0.71971}, +{-0.712534, 0.694112, -0.10249}, +{-0.16661, -0.662629, -0.730181}, +{-0.38104, 0.586075, -0.71507}, +{0.355708, -0.927167, -0.117616}, +{-0.349585, -0.754687, 0.555191}, +{-0.344679, -0.925227, 0.158591}, +{0.958463, -0.127976, -0.254892}, +{0.277561, -0.406741, -0.870357}, +{0.271495, -0.783638, 0.55875}, +{0.721991, -0.365288, 0.587617}, +{-0.780681, 0.458752, 0.424362}, +{-0.14267, 0.285737, 0.947628}, +{-0.847507, -0.516206, 0.123544}, +{0.17679, -0.0355374, -0.983607}, +{-0.00886629, -0.981831, 0.189548}, +{-0.83529, 0.134398, 0.53313}, +{-0.209787, 0.710618, -0.671574}, +{-0.688872, -0.574981, 0.441421}, +{0.000579968, -0.798947, 0.601401}, +{0.022314, 0.999066, 0.0369917}, +{0.569746, -0.777583, -0.265996}, +{-0.432933, -0.275586, 0.858266}, +{-0.168233, 0.664311, 0.728278}, +{0.143881, 0.930776, -0.336085}, +{-0.739684, 0.339837, 0.580842}, +{0.402484, -0.854786, -0.32764}, +{0.188465, 0.536313, -0.822708}, +{-0.893367, -0.167853, 0.416799}, +{0.0463878, -0.989504, -0.136859}, +{0.891515, -0.146137, -0.428772}, +{-0.270978, 0.403364, 0.873995}, +{0.909623, -0.312123, -0.274165}, +{-0.0645389, -0.764963, 0.640832}, +{-0.860568, -0.127019, -0.493243}, +{0.412006, 0.401924, -0.817746}, +{-0.856483, -0.377985, 0.351518}, +{-0.519026, -0.769465, 0.372203}, +{-0.281538, -0.153918, 0.947125}, +{0.535697, 0.0817147, -0.840447}, +{0.176875, 0.98376, -0.0305112}, +{-0.953555, -0.266499, -0.140398}, +{-0.729252, 0.398091, 0.556521}, +{0.905964, 0.156002, -0.393564}, +{-0.355032, 0.0749636, 0.931844}, +{-0.223214, -0.434299, 0.872674}, +{-0.944726, -0.289382, -0.154116}, +{-0.448393, 0.674572, -0.586427}, +{-0.712834, -0.41273, 0.567029}, +{0.79357, -0.0793679, -0.603281}, +{0.264879, 0.242192, 0.933371}, +{-0.0846282, 0.286725, -0.954268}, +{0.929102, 0.138991, 0.342711}, +{-0.227705, -0.956085, 0.184533}, +{-0.187085, 0.205396, 0.960631}, +{0.859723, -0.0182001, 0.510436}, +{0.54603, 0.371129, 0.751075}, +{-0.58917, -0.80652, 0.0490214}, +{-0.80694, -0.5828, 0.0958714}, +{0.053987, -0.489671, -0.870234}, +{0.539427, -0.560059, -0.628771}, +{-0.719461, 0.400497, -0.567431}, +{0.0885711, -0.995959, -0.0148576}, +{-0.448824, 0.696023, -0.560454}, +{0.0815733, 0.947985, -0.307686}, +{0.565003, 0.408393, 0.716929}, +{-0.98329, -0.0905731, 0.157918}, +{0.841385, 0.539693, -0.0283297}, +{0.550516, -0.412127, 0.726005}, +{0.295579, 0.305102, -0.905288}, +{0.391698, 0.697622, -0.599913}, +{-0.810178, 0.408264, -0.420634}, +{0.790772, 0.391651, 0.470413}, +{0.109123, 0.984543, -0.136996}, +{-0.978166, 0.128207, -0.163567}, +{-0.6696, -0.230418, 0.706076}, +{0.849107, -0.429713, 0.307188}, +{0.625393, -0.639498, -0.447131}, +{-0.775775, 0.382199, 0.502093}, +{-0.128569, 0.393416, 0.910326}, +{-0.201091, 0.282941, 0.93782}, +{-0.143928, 0.72237, 0.676363}, +{-0.395031, 0.313312, 0.863589}, +{0.0487655, -0.987901, 0.14722}, +{0.788709, 0.421011, -0.447982}, +{-0.998818, -0.0137354, -0.0466272}, +{-0.467379, 0.884057, -6.55763e-05}, +{-0.158109, 0.469027, -0.868916}, +{0.921152, 0.32576, 0.212976}, +{-0.716247, -0.421887, -0.555879}, +{-0.2676, 0.922002, -0.279825}, +{0.709843, 0.698078, -0.0938618}, +{-0.421407, -0.835698, 0.352172}, +{-0.588552, 0.33064, -0.737756}, +{-0.874213, 0.283041, 0.394511}, +{0.959154, 0.180283, 0.217993}, +{0.978598, -0.166951, -0.120305}, +{0.592886, -0.768003, 0.242194}, +{0.955255, -0.134192, 0.26359}, +{-0.897975, 0.431359, 0.0870076}, +{-0.594419, -0.492108, -0.636}, +{-0.0661818, 0.993617, -0.0913517}, +{0.0724074, 0.983393, 0.16642}, +{0.794781, 0.470349, 0.38353}, +{-0.842681, 0.538412, -0.00119135}, +{0.942937, -0.2129, -0.256016}, +{0.497282, 0.086389, -0.863277}, +{0.0376349, -0.954772, 0.294949}, +{-0.808374, 0.0377819, 0.587456}, +{-0.0538058, 0.563107, 0.82463}, +{0.139309, 0.947273, 0.288559}, +{-0.413041, 0.737929, 0.533721}, +{0.93779, 0.331418, 0.103501}, +{-0.307696, -0.9472, 0.0901992}, +{-0.351043, -0.352512, 0.86747}, +{0.44842, -0.0205, -0.893588}, +{0.98002, -0.18045, -0.0836551}, +{-0.783922, -0.378148, -0.492413}, +{0.254744, 0.0321739, 0.966473}, +{-0.361177, 0.717579, -0.59551}, +{-0.287109, 0.929941, 0.229735}, +{-0.796506, 0.603541, -0.0362831}, +{0.213866, 0.805984, -0.551952}, +{-0.206413, -0.886837, -0.413418}, +{-0.942808, -0.177519, -0.282134}, +{-0.953864, -0.047572, 0.296448}, +{-0.108725, -0.80676, 0.580791}, +{0.655167, -0.575531, 0.489408}, +{-0.26821, -0.961313, -0.062776}, +{0.4394, 0.77673, 0.451241}, +{0.734085, -0.324041, 0.596755}, +{-0.120679, 0.949922, 0.288246}, +{0.552587, 0.831591, 0.0557122}, +{-0.145806, -0.201449, 0.968586}, +{-0.348096, 0.325704, -0.879059}, +{-0.363671, -0.854669, -0.370519}, +{-0.609951, -0.768363, 0.193851}, +{-0.0428035, 0.981242, -0.187966}, +{0.199488, -0.956854, 0.21127}, +{0.354041, -0.361824, -0.862403}, +{-0.700101, -0.711521, -0.0599708}, +{0.669507, -0.0823363, 0.738229}, +{-0.973178, 0.17072, -0.154206}, +{-0.844855, -0.391564, 0.364552}, +{-0.964624, -0.254598, 0.0684139}, +{0.715911, -0.683872, -0.140676}, +{-0.869399, -0.269567, -0.4141}, +{-0.910118, 0.0397934, 0.412435}, +{0.599398, 0.654847, -0.460323}, +{-0.858494, 0.373578, 0.351324}, +{0.20047, -0.622694, -0.756349}, +{-0.791004, 0.191885, -0.580941}, +{0.284342, -0.674869, 0.680956}, +{-0.858334, -0.506982, 0.0789444}, +{0.269879, 0.698302, 0.662977}, +{0.323858, -0.766505, 0.554605}, +{-0.707329, 0.6999, 0.0991286}, +{0.867644, 0.0826637, -0.490265}, +{0.227606, 0.856332, 0.463564}, +{0.30059, -0.944471, 0.132739}, +{-0.637241, 0.0615798, -0.7682}, +{0.638011, 0.645645, 0.419623}, +{-0.927098, -0.230183, -0.295813}, +{-0.659675, 0.301357, 0.688486}, +{-0.777926, 0.628039, -0.0199492}, +{-0.605243, -0.115895, -0.787559}, +{-0.0654718, 0.962462, 0.263402}, +{-0.352416, 0.86862, -0.348285}, +{-0.110457, -0.806032, -0.581474}, +{0.197528, 0.750607, 0.630533}, +{0.755926, -0.536331, -0.375399}, +{0.821056, 0.223586, -0.525239}, +{-0.664956, 0.253612, -0.702506}, +{0.201518, -0.0328747, 0.978933}, +{-0.209827, -0.73845, 0.640831}, +{0.796235, 0.59036, -0.132227}, +{-0.915725, 0.352343, 0.193139}, +{-0.407448, 0.0368511, 0.912485}, +{0.11229, 0.929486, 0.35135}, +{0.749146, 0.662311, -0.0111464}, +{0.905993, 0.324275, -0.27207}, +{0.407324, -0.890735, -0.201689}, +{0.169786, 0.367227, 0.914504}, +{-0.191178, -0.530373, 0.825927}, +{-0.787989, -0.0355933, -0.614659}, +{-0.144838, -0.955181, -0.258169}, +{0.753115, 0.158211, 0.638582}, +{-0.306448, -0.162808, -0.937861}, +{-0.489577, 0.278288, -0.82636}, +{-0.72246, 0.590467, 0.359723}, +{0.287388, -0.935619, -0.204999}, +{-0.882148, 0.47034, -0.02441}, +{-0.471464, -0.880318, -0.0525503}, +{-0.796874, 0.595351, 0.102711}, +{0.271123, 0.649341, 0.710527}, +{-0.37685, -0.669364, -0.640263}, +{0.703322, 0.686989, -0.182716}, +{0.24842, 0.756239, 0.605302}, +{0.53306, 0.254665, -0.806841}, +{0.842756, -0.421621, -0.334661}, +{-0.208891, -0.977877, 0.0109621}, +{-0.801054, -0.215962, -0.558277}, +{-0.519437, -0.0747833, 0.85123}, +{0.0836781, -0.772379, -0.629625}, +{-0.774349, -0.330209, 0.539764}, +{0.0382409, 0.106411, -0.993587}, +{-0.816217, 0.230689, 0.529691}, +{0.014752, -0.949014, 0.314888}, +{0.431221, 0.894449, -0.118362}, +{-0.956352, -0.225694, 0.185616}, +{0.829542, -0.555226, 0.0598695}, +{-0.386713, 0.907231, -0.165487}, +{-0.901075, -0.17875, -0.39511}, +{-0.749729, -0.64827, -0.132861}, +{-0.562157, -0.146526, -0.813947}, +{0.0990264, 0.0472287, 0.993963}, +{0.36293, 0.589729, -0.721458}, +{0.0500146, 0.124626, -0.990942}, +{0.959005, -0.251085, 0.131401}, +{-0.541522, -0.799311, -0.260492}, +{0.991897, 0.0197292, -0.125507}, +{-0.936784, -0.349463, -0.0176257}, +{0.916422, -0.230106, -0.327446}, +{0.415317, 0.296418, 0.860028}, +{-0.731054, 0.588311, -0.345617}, +{-0.811151, 0.342511, 0.474046}, +{0.255074, 0.890566, 0.376602}, +{-0.231586, -0.623258, 0.746938}, +{-0.734428, -0.384482, -0.559276}, +{0.583332, -0.756235, -0.296365}, +{-0.060311, -0.302921, -0.951105}, +{0.297677, 0.837406, -0.458411}, +{-0.588758, 0.722114, 0.363201}, +{-0.49972, 0.0663795, -0.86364}, +{-0.737663, -0.0863525, -0.669624}, +{0.32931, 0.435062, -0.838019}, +{0.207166, 0.616965, -0.759234}, +{0.689518, 0.0685701, -0.721015}, +{0.391154, -0.868447, -0.304629}, +{0.0164783, -0.731568, 0.681569}, +{0.280243, 0.56566, -0.77556}, +{0.314565, 0.49037, 0.812765}, +{-0.512091, 0.811192, -0.282368}, +{-0.564512, 0.817313, -0.115436}, +{0.99433, 0.0633357, -0.0854221}, +{-0.0500625, -0.671179, -0.739603}, +{0.816401, -0.529016, 0.231583}, +{0.252949, 0.942806, 0.217103}, +{-0.70603, 0.139916, -0.694223}, +{-0.61635, -0.397144, 0.679992}, +{0.731873, -0.540254, 0.415316}, +{-0.807739, 0.369344, -0.459503}, +{0.0360823, -0.955668, 0.292228}, +{0.616608, -0.449479, -0.646346}, +{-0.942669, -0.3187, 0.0990219}, +{-0.341522, 0.566341, -0.75008}, +{-0.8386, -0.263182, 0.476954}, +{-0.270799, -0.0591661, -0.960816}, +{0.837619, -0.53241, 0.122207}, +{-0.765306, -0.349541, -0.540488}, +{0.649025, -0.217449, -0.729028}, +{-0.557545, -0.513753, -0.652075}, +{0.157804, -0.987459, 0.0048134}, +{0.693179, 0.507162, 0.512142}, +{0.382248, 0.906845, 0.177537}, +{-0.560155, 0.645224, 0.519531}, +{0.474783, -0.87827, -0.0567782}, +{-0.813674, -0.243869, 0.527695}, +{-0.355341, -0.390255, 0.849373}, +{0.655591, 0.736493, 0.166671}, +{0.0197052, -0.356799, -0.933973}, +{0.980937, 0.00742103, 0.194183}, +{-0.229748, -0.270781, 0.934823}, +{-0.265678, -0.686185, -0.677174}, +{-0.450957, 0.721692, -0.525165}, +{0.000324749, -0.866542, -0.499104}, +{-0.553754, -0.680856, -0.479367}, +{-0.264192, 0.796437, -0.543958}, +{-0.392973, 0.741755, -0.543482}, +{0.702389, 0.697604, -0.141416}, +{0.714674, -0.469297, 0.518654}, +{-0.402911, 0.421222, -0.812549}, +{0.264147, -0.68831, -0.675615}, +{0.78921, -0.582286, -0.19517}, +{-0.0824477, -0.00116425, 0.996595}, +{-0.324806, 0.840263, -0.43412}, +{0.887715, 0.129521, 0.441799}, +{0.597744, 0.79876, -0.0684474}, +{-0.207112, -0.275587, 0.938699}, +{-0.575629, 0.599987, -0.555578}, +{0.719725, 0.397451, 0.569235}, +{-0.959087, -0.076287, -0.272641}, +{0.796882, -0.354669, 0.489069}, +{0.708458, 0.448478, 0.544935}, +{-0.665336, 0.516664, 0.538875}, +{-0.911548, 0.0911464, -0.400964}, +{0.776899, -0.614433, -0.137477}, +{-0.871522, -0.170681, -0.459693}, +{-0.870652, 0.354273, -0.341256}, +{0.0942658, -0.9856, -0.140379}, +{0.726113, 0.686427, 0.0397239}, +{-0.397278, -0.822286, 0.40745}, +{-0.697302, -0.298496, -0.651667}, +{0.191794, -0.947671, 0.255215}, +{0.735911, -0.645858, 0.203229}, +{0.0600239, 0.53747, 0.841144}, +{0.14828, 0.615972, 0.773687}, +{-0.134888, 0.353702, -0.925581}, +{-0.069339, -0.991692, 0.10835}, +{0.589593, -0.0975273, 0.801791}, +{0.783441, -0.16331, 0.599625}, +{0.527139, 0.745402, 0.408044}, +{0.102921, -0.11983, -0.987445}, +{-0.0599521, -0.851733, 0.520535}, +{-0.608276, 0.784458, 0.120939}, +{-0.515247, -0.000222423, -0.857042}, +{-0.61477, -0.358084, -0.702733}, +{0.0994203, 0.0483643, -0.993869}, +{-0.75529, 0.653116, 0.0545559}, +{-0.503755, -0.250575, 0.826706}, +{-0.899874, 0.118314, 0.419796}, +{-0.688041, -0.669725, 0.279408}, +{0.0910559, 0.6365, -0.765883}, +{0.0381416, 0.975596, 0.216237}, +{-0.265139, -0.703951, 0.658904}, +{0.978556, 0.0848833, 0.187678}, +{0.14083, -0.621125, 0.770955}, +{-0.176196, 0.973474, 0.145955}, +{0.565634, -0.057248, 0.822667}, +{0.753695, -0.575597, -0.317225}, +{0.887132, -0.299991, -0.350717}, +{-0.230457, -0.156426, 0.960427}, +{0.701721, -0.474333, -0.531598}, +{-0.110296, 0.630341, -0.768443}, +{0.501116, 0.824596, 0.262533}, +{-0.374546, -0.637327, 0.673446}, +{-0.383725, 0.464229, -0.798277}, +{0.0580225, -0.967883, 0.244615}, +{0.551548, 0.560137, -0.618095}, +{-0.101881, -0.46053, -0.881778}, +{-0.612479, -0.7873, 0.0709087}, +{0.390486, 0.852635, 0.347182}, +{0.458075, 0.371163, 0.807716}, +{0.613617, -0.783779, -0.0957313}, +{-0.556525, 0.400959, 0.727675}, +{0.499381, 0.682695, 0.533429}, +{0.984063, 0.150016, 0.0954694}, +{0.471225, -0.88199, -0.00636732}, +{-0.811055, -0.450552, -0.373085}, +{-0.566329, 0.648078, -0.509182}, +{-0.748088, 0.288496, -0.597608}, +{-0.549832, 0.830465, 0.0895167}, +{-0.0493562, 0.876603, -0.478677}, +{0.295538, -0.767647, 0.56866}, +{-0.266756, 0.0178255, 0.963599}, +{-0.344263, -0.130044, -0.929823}, +{0.452946, 0.861469, -0.22959}, +{0.391077, 0.827886, -0.402075}, +{-0.701277, -0.133904, -0.700201}, +{0.632009, 0.673669, -0.383059}, +{0.282575, -0.22528, -0.932416}, +{-0.639501, 0.757424, -0.13171}, +{0.0487892, -0.862856, 0.503089}, +{-0.0426833, -0.405237, -0.913215}, +{-0.307688, 0.466109, 0.8295}, +{-0.0783323, 0.932435, 0.352744}, +{-0.245772, -0.872554, -0.422192}, +{-0.105561, -0.253903, 0.961452}, +{-0.698081, -0.696223, 0.167203}, +{-0.857101, 0.121097, -0.500712}, +{0.0210134, 0.788351, 0.614866}, +{0.5518, 0.175332, 0.815338}, +{0.270618, 0.188587, 0.944034}, +{-0.0780988, 0.641501, -0.763136}, +{-0.99306, 0.0134998, 0.116832}, +{-0.483049, 0.524776, -0.70091}, +{0.750752, -0.31133, 0.582619}, +{0.812438, -0.152253, -0.562818}, +{0.820164, 0.340432, 0.459822}, +{0.158244, 0.369461, 0.915673}, +{0.275361, 0.793618, -0.542537}, +{0.893993, -0.350854, 0.278706}, +{-0.821305, 0.55538, 0.130428}, +{-0.34892, 0.808745, -0.473483}, +{0.556329, -0.805549, 0.203934}, +{0.387127, -0.0419158, 0.921073}, +{0.691026, 0.245518, 0.679856}, +{0.00266877, 0.940652, 0.339361}, +{-0.422848, -0.540347, 0.727478}, +{0.0406423, 0.916294, -0.398438}, +{0.992862, 0.0234687, 0.116939}, +{0.499208, 0.855739, -0.136022}, +{-0.26282, -0.959319, 0.103116}, +{0.721329, 0.403828, -0.562679}, +{-0.802475, -0.419347, 0.424478}, +{-0.671164, -0.426121, 0.606596}, +{-0.961493, 0.145253, -0.233309}, +{0.0934428, -0.268941, 0.958613}, +{-0.554441, -0.827452, -0.08899}, +{0.611902, 0.548183, -0.57015}, +{-0.147962, 0.062953, -0.986987}, +{-0.707904, -0.352186, 0.612239}, +{0.56187, -0.32778, -0.759515}, +{-0.328466, 0.406813, 0.852416}, +{0.26952, -0.311998, 0.911052}, +{0.0490865, 0.373432, 0.926358}, +{0.580108, 0.581883, -0.569988}, +{0.226997, -0.182859, 0.956575}, +{-0.943251, -0.0678514, 0.325076}, +{-0.687003, -0.323428, 0.650708}, +{-0.614447, -0.787446, 0.0488158}, +{-0.00400651, -0.517805, 0.855489}, +{-0.784443, 0.0730019, -0.615889}, +{-0.61713, 0.379243, -0.689438}, +{-0.0100767, 0.658857, 0.7522}, +{0.370795, -0.0119834, -0.928638}, +{0.412538, -0.890299, 0.192823}, +{0.456043, 0.0417046, -0.88898}, +{-0.119861, -0.869084, 0.479924}, +{-0.0910592, 0.850369, -0.518247}, +{-0.289817, -0.618825, -0.730111}, +{-0.454023, -0.81219, -0.366348}, +{0.375905, 0.898207, 0.227857}, +{-0.420898, -0.783026, -0.457947}, +{-0.76111, -0.276577, 0.5867}, +{0.422564, 0.67762, -0.60189}, +{-0.250672, 0.962642, 0.10239}, +{-0.3982, 0.620009, -0.676037}, +{0.760354, 0.632939, -0.145776}, +{0.977373, -0.208068, 0.038065}, +{-0.420687, 0.722836, 0.548206}, +{0.425622, -0.109979, 0.898193}, +{0.10304, -0.98795, -0.115486}, +{0.235739, 0.969748, 0.0633673}, +{-0.914286, -0.335358, -0.227193}, +{0.584215, 0.787032, -0.198177}, +{0.14236, 0.571359, 0.808259}, +{0.5992, 0.333021, 0.728049}, +{-0.22739, 0.53179, 0.815778}, +{-0.658242, 0.538057, -0.52651}, +{0.449638, -0.715824, -0.534249}, +{0.0791765, 0.276292, 0.957807}, +{0.603934, -0.79669, 0.0234157}, +{0.821562, -0.203038, -0.532739}, +{-0.0976596, -0.691315, 0.715923}, +{-0.0589546, 0.673401, -0.736923}, +{-0.914426, 0.241248, -0.325001}, +{0.161471, 0.982652, -0.0912233}, +{-0.336509, 0.397844, 0.853512}, +{-0.420388, 0.873615, -0.245094}, +{0.0640444, 0.84527, 0.530487}, +{-0.0352455, 0.82385, -0.565711}, +{-0.534704, -0.280703, 0.797055}, +{0.885905, 0.262922, -0.382159}, +{-0.0562351, -0.507994, -0.859523}, +{0.592719, 0.734782, 0.329817}, +{0.926818, -0.343725, -0.151199}, +{0.826178, -0.250624, 0.504596}, +{0.931813, -0.34272, -0.119444}, +{-0.342231, -0.443432, 0.8284}, +{0.692905, -0.576394, 0.433188}, +{-0.918566, -0.186893, 0.348293}, +{-0.836432, 0.392508, 0.382517}, +{-0.696492, 0.683553, 0.218299}, +{0.78663, -0.213412, -0.57937}, +{0.588504, 0.345673, -0.730871}, +{-0.477699, -0.63261, -0.609597}, +{-0.97697, -0.10188, 0.187483}, +{0.703413, 0.623665, 0.340956}, +{0.607495, 0.253386, 0.752825}, +{0.37487, -0.32849, 0.86693}, +{-0.4972, -0.169743, 0.85087}, +{0.535864, 0.214694, -0.816552}, +{-0.382474, -0.547716, -0.744124}, +{0.670379, -0.364221, 0.646479}, +{0.203084, -0.470813, -0.858541}, +{0.971462, -0.0613605, -0.229121}, +{-0.96844, 0.160011, 0.191103}, +{-0.137076, -0.347716, -0.927525}, +{0.805887, -0.511273, -0.298573}, +{0.534396, 0.105123, 0.838672}, +{0.279691, 0.441594, -0.852507}, +{-0.37633, -0.633009, -0.676517}, +{-0.846924, 0.500161, 0.180441}, +{-0.480585, 0.875334, 0.0531841}, +{-0.66332, 0.597015, 0.451198}, +{0.444799, -0.234479, 0.864392}, +{0.664007, -0.293749, 0.687609}, +{-0.851509, 0.0515746, 0.521798}, +{0.26895, -0.822463, 0.50122}, +{0.903352, -0.397565, 0.160925}, +{-0.20598, 0.783378, 0.586423}, +{-0.547457, 0.636329, -0.543486}, +{-0.167001, 0.983989, -0.0622531}, +{0.482504, -0.0222271, -0.875611}, +{0.470896, -0.0533223, -0.880576}, +{0.711537, -0.694893, -0.104108}, +{0.599124, -0.55873, -0.573472}, +{-0.474005, -0.797367, 0.37353}, +{0.66566, -0.635605, 0.391028}, +{0.689823, 0.165019, 0.70492}, +{0.857426, -0.163248, 0.488027}, +{0.341714, 0.939745, -0.0105444}, +{0.575619, -0.80597, -0.138111}, +{-0.759326, -0.390086, 0.520823}, +{-0.412759, 0.690952, 0.593478}, +{0.844524, -0.219406, -0.488508}, +{-0.411155, 0.906954, -0.0915791}, +{-0.0734946, 0.814459, -0.575547}, +{-0.318468, -0.112749, 0.941204}, +{0.48634, -0.351264, 0.800054}, +{-0.366569, -0.883893, -0.290449}, +{-0.482288, 0.875982, -0.00729551}, +{-0.586016, 0.191328, -0.787387}, +{-0.0589872, -0.861879, -0.503671}, +{0.619175, -0.27122, 0.736927}, +{-0.431462, 0.851609, 0.297661}, +{-0.519697, 0.769096, -0.37203}, +{0.979585, -0.176167, 0.0968469}, +{0.139623, -0.0885551, 0.986237}, +{0.74248, -0.412116, 0.528095}, +{-0.289222, 0.925695, -0.243802}, +{0.843438, -0.13667, -0.519551}, +{-0.0551621, 0.987593, -0.147028}, +{0.405689, 0.904573, 0.131012}, +{-0.659906, -0.28067, -0.696956}, +{0.875922, 0.0394177, -0.480839}, +{0.214099, 0.954068, -0.20956}, +{-0.807528, 0.0768144, 0.584806}, +{-0.909901, -0.126205, -0.395161}, +{0.34116, 0.418761, 0.841575}, +{-0.28513, 0.947921, -0.141939}, +{-0.706284, -0.576038, -0.411513}, +{0.85137, -0.317163, -0.417823}, +{0.0188583, 0.107301, 0.994048}, +{0.64713, 0.649876, -0.398603}, +{0.138254, 0.724492, -0.675275}, +{0.354867, 0.400039, -0.845008}, +{-0.938112, -0.0234211, 0.34554}, +{0.742036, 0.191766, 0.642346}, +{0.856621, 0.5028, 0.11573}, +{-0.434021, -0.0227692, -0.900615}, +{-0.601026, 0.741429, -0.298414}, +{-0.673475, -0.732513, 0.0992782}, +{-0.662541, -0.379582, 0.645722}, +{-0.995907, 0.0250044, 0.0868546}, +{-0.782266, -0.264561, 0.563974}, +{0.200858, -0.835267, -0.511845}, +{-0.949537, -0.309475, -0.0510352}, +{-0.388429, -0.723384, 0.570823}, +{-0.320672, -0.276452, 0.905949}, +{-0.357194, -0.933937, -0.0131631}, +{0.0885846, 0.953358, 0.288549}, +{-0.145435, 0.657618, -0.73918}, +{0.652278, -0.618788, 0.437761}, +{-0.22985, 0.171973, -0.957911}, +{-0.578777, -0.781129, 0.23421}, +{-0.94286, 0.111042, -0.314142}, +{0.937126, 0.181461, 0.298104}, +{-0.179534, -0.38247, -0.906358}, +{-0.335612, 0.11261, -0.935245}, +{-0.668934, 0.675692, -0.309786}, +{0.0649965, 0.76264, -0.643549}, +{0.576883, -0.80071, -0.161458}, +{0.364489, -0.853905, 0.371477}, +{-0.112131, 0.0117413, 0.993624}, +{0.396367, -0.766435, -0.505441}, +{-0.0716628, -0.921908, 0.380723}, +{0.460602, -0.456948, -0.760949}, +{-0.0161275, -0.210673, 0.977424}, +{0.0955965, -0.113385, 0.988941}, +{-0.0706746, 0.551821, -0.830963}, +{0.56705, 0.802861, 0.184035}, +{0.868758, 0.4178, 0.265899}, +{0.412118, 0.793233, -0.448263}, +{0.717836, 0.286747, 0.634419}, +{0.879291, -0.0473743, -0.473922}, +{0.435926, 0.875095, -0.210183}, +{-0.716205, -0.547931, -0.432228}, +{-0.664407, 0.444246, 0.601007}, +{-0.754116, 0.486444, -0.441227}, +{0.396667, 0.903096, 0.164537}, +{-0.400434, -0.890879, -0.214447}, +{-0.745848, -0.236597, -0.622681}, +{-0.227406, 0.281604, -0.932194}, +{-0.793889, -0.498427, -0.348297}, +{0.157677, 0.90256, 0.400653}, +{0.764759, -0.0358843, 0.643317}, +{-0.75116, -0.180826, 0.634871}, +{0.202384, -0.892214, 0.403727}, +{-0.18349, -0.942192, -0.280366}, +{0.874438, 0.413068, 0.254428}, +{-0.446354, -0.801995, 0.396954}, +{0.302395, 0.232331, 0.924435}, +{0.9458, -0.00947256, 0.324611}, +{-0.498827, -0.301202, -0.81268}, +{-0.819413, -0.56827, -0.0750448}, +{0.613679, 0.269306, -0.742208}, +{0.729911, 0.679867, 0.0707929}, +{-0.513902, 0.133425, -0.84741}, +{0.793504, -0.418047, 0.442252}, +{0.867076, -0.0788834, -0.49189}, +{0.0729151, 0.962485, -0.261355}, +{0.0547217, 0.997401, -0.0468684}, +{0.261088, -0.193653, 0.945691}, +{0.340297, -0.76409, -0.548056}, +{-0.13768, 0.967915, 0.2102}, +{-0.562329, 0.773879, 0.291371}, +{0.0665066, -0.160785, 0.984746}, +{-0.202433, -0.602326, 0.772156}, +{-0.272733, -0.0856947, 0.958266}, +{0.129646, 0.476455, 0.869588}, +{-0.86035, -0.424429, -0.282239}, +{-0.760899, -0.476438, -0.4405}, +{-0.884681, -0.424358, -0.193027}, +{-0.0335591, -0.964762, -0.260975}, +{0.774665, 0.180569, -0.606043}, +{0.126965, -0.983371, 0.129854}, +{0.855729, -0.447714, 0.259383}, +{-0.0957576, 0.47037, 0.877259}, +{0.727538, -0.670938, 0.143288}, +{-0.35429, 0.761615, -0.542607}, +{-0.15773, -0.602513, -0.782368}, +{-0.216273, -0.873181, 0.436785}, +{0.771473, -0.195369, -0.605525}, +{-0.585473, 0.00406065, -0.810682}, +{-0.629023, 0.282473, -0.724251}, +{-0.514511, -0.0281528, -0.857022}, +{-0.322343, 0.0095765, 0.946575}, +{-0.328835, -0.917579, 0.22342}, +{-0.877527, 0.170455, 0.448209}, +{-0.550416, 0.820332, 0.155233}, +{-0.53004, -0.373799, 0.761139}, +{0.991286, -0.0512702, -0.121341}, +{0.292256, -0.907491, -0.301741}, +{-0.447908, -0.893356, 0.0359559}, +{0.452801, -0.891305, 0.0233921}, +{0.232876, -0.925686, -0.298117}, +{0.690013, 0.0977649, 0.717164}, +{-0.79073, -0.162773, 0.590128}, +{0.778195, 0.347253, -0.523286}, +{0.999156, -0.0351757, 0.0211937}, +{-0.473106, 0.0664528, 0.878496}, +{-0.132027, 0.413395, -0.900929}, +{0.820559, -0.295866, -0.489026}, +{-0.963961, 0.251308, 0.0873173}, +{-0.295447, -0.248527, 0.922467}, +{-0.175424, 0.214746, -0.960787}, +{0.2743, -0.262314, -0.925176}, +{-0.237052, -0.545282, 0.804036}, +{0.830063, 0.294873, 0.473335}, +{-0.386738, -0.651831, 0.652342}, +{0.512608, 0.622919, 0.590936}, +{0.88089, -0.404167, 0.246337}, +{0.128466, 0.802582, -0.582545}, +{0.170673, 0.964959, -0.199311}, +{-0.672145, 0.22923, 0.704042}, +{-0.921697, 0.312007, 0.230493}, +{0.707309, 0.573764, 0.412927}, +{-0.18929, 0.543182, 0.817999}, +{0.959745, -0.040652, -0.277917}, +{-0.153033, 0.869658, 0.469335}, +{-0.375193, 0.608997, 0.698822}, +{0.53271, 0.842274, -0.0824327}, +{0.131195, -0.48388, -0.865245}, +{0.602316, 0.797635, 0.0315262}, +{0.136939, -0.184944, 0.973162}, +{0.994643, -0.0319982, 0.0982889}, +{-0.444215, 0.891042, 0.093364}, +{0.988759, -0.117902, -0.091952}, +{-0.548264, 0.177387, -0.817276}, +{-0.339645, 0.386917, -0.857284}, +{0.689515, -0.102579, -0.716971}, +{-0.91773, -0.392896, -0.058344}, +{0.106747, 0.0735383, 0.991563}, +{0.0276432, -0.708076, -0.705595}, +{-0.38965, -0.467323, -0.793588}, +{-0.0131298, 0.895868, -0.444126}, +{0.665809, -0.625251, -0.407136}, +{-0.12515, -0.894627, 0.42893}, +{-0.833045, 0.478715, -0.27725}, +{0.454395, 0.772666, 0.443297}, +{0.987237, -0.00730941, -0.15909}, +{0.414135, -0.57278, -0.7074}, +{-0.324942, -0.72619, -0.605856}, +{0.627281, -0.510358, 0.588263}, +{0.439789, -0.484421, -0.756255}, +{-0.652242, 0.757978, 0.00704881}, +{-0.373942, 0.899618, -0.225509}, +{0.825105, 0.562852, 0.0489898}, +{-0.63233, -0.767614, 0.104532}, +{0.820989, -0.569476, -0.0409113}, +{0.105557, -0.672073, 0.732923}, +{-0.546815, -0.643188, -0.536006}, +{-0.98421, 0.122936, -0.127347}, +{-0.937835, -0.301199, -0.172468}, +{-0.798297, 0.331381, 0.502901}, +{0.876577, -0.320116, 0.359359}, +{-0.873652, -0.0792937, -0.480047}, +{0.872683, -0.331316, 0.358683}, +{-0.0756679, 0.911254, -0.404834}, +{-0.628316, 0.754725, -0.188702}, +{0.90848, 0.0959191, -0.406772}, +{-0.611576, -0.614064, 0.4989}, +{0.665643, -0.228774, 0.710339}, +{0.296074, 0.147245, -0.943748}, +{-0.771641, -0.186052, -0.608239}, +{0.0631991, -0.186638, -0.980394}, +{0.327353, 0.754274, -0.569131}, +{-0.496307, -0.0897301, 0.863497}, +{0.114655, -0.640038, -0.75974}, +{0.95382, 0.253717, 0.160795}, +{0.00368731, 0.0292344, -0.999566}, +{-0.225998, 0.506869, 0.831871}, +{0.0155505, -0.628994, 0.777255}, +{-0.164043, 0.800941, 0.575833}, +{-0.8012, -0.520804, 0.294689}, +{-0.37557, -0.652561, 0.658112}, +{0.565823, 0.614945, 0.549261}, +{0.176254, 0.966684, -0.185625}, +{0.390336, -0.748052, 0.536708}, +{0.818069, -0.557636, -0.140732}, +{-0.517473, 0.349365, -0.781131}, +{-0.162016, -0.450023, -0.878197}, +{0.577226, 0.289765, -0.763444}, +{-0.690688, -0.629412, -0.356078}, +{0.482653, -0.630833, 0.607532}, +{-0.858363, 0.282404, -0.428323}, +{0.310449, 0.94967, -0.0418089}, +{-0.722421, 0.194888, 0.663421}, +{-0.644558, 0.485278, -0.590805}, +{-0.162559, -0.965168, 0.205}, +{0.581464, 0.755073, -0.302925}, +{-0.898809, 0.427672, 0.0961232}, +{0.404517, -0.755253, -0.515711}, +{-0.270952, 0.790808, 0.548824}, +{-0.908789, 0.338761, -0.243603}, +{-0.529178, -0.212368, -0.821505}, +{-0.220407, 0.390744, 0.893723}, +{-0.932191, -0.0588506, -0.35715}, +{-0.98006, -0.182215, -0.0792498}, +{0.30589, -0.529102, 0.791507}, +{0.795032, 0.332284, -0.507456}, +{0.178782, 0.982033, 0.0604059}, +{0.147488, -0.600992, -0.785529}, +{0.495385, -0.21943, -0.840502}, +{-0.397787, -0.91367, 0.0834989}, +{0.973768, -0.225909, -0.0272409}, +{0.831056, 0.149098, -0.535832}, +{0.517933, 0.831696, 0.200068}, +{0.349003, -0.420383, -0.837541}, +{0.954388, 0.120516, -0.273164}, +{-0.427603, -0.457066, 0.779902}, +{0.742244, 0.223035, -0.631925}, +{-0.72167, 0.633715, 0.278565}, +{0.767102, -0.105158, 0.632848}, +{-0.325012, 0.941518, -0.0889433}, +{0.704233, 0.692471, 0.156652}, +{0.754003, -0.444778, -0.483377}, +{0.865427, 0.41148, -0.285866}, +{0.457081, -0.759775, 0.462406}, +{-0.790932, -0.378616, -0.480704}, +{0.0755721, 0.996668, 0.0307027}, +{-0.23453, 0.617231, -0.751013}, +{0.238734, 0.820636, -0.519194}, +{0.0536378, 0.312401, 0.948435}, +{-0.708959, -0.703884, 0.0438579}, +{0.240132, -0.94957, 0.201627}, +{0.502017, 0.640009, 0.581693}, +{-0.307768, 0.0268541, -0.951082}, +{0.209938, -0.770832, -0.601452}, +{-0.322063, -0.165027, 0.932224}, +{-0.235302, -0.97144, -0.0305988}, +{-0.0350638, -0.556001, -0.830442}, +{0.969674, -0.204291, 0.134154}, +{0.0810517, -0.981262, 0.174803}, +{0.501862, -0.859798, -0.0942477}, +{0.784721, -0.465198, 0.409639}, +{-0.0843505, 0.238352, -0.967509}, +{-0.320688, -0.0431963, 0.946199}, +{-0.290557, -0.662391, -0.690517}, +{0.0693549, 0.442012, -0.894324}, +{-0.371877, -0.429114, -0.823146}, +{0.655676, -0.594403, -0.465591}, +{0.0548481, 0.80823, 0.586307}, +{0.330308, -0.124721, 0.935597}, +{-0.226417, 0.0238827, 0.973738}, +{-0.74498, 0.574555, 0.338956}, +{-0.527968, -0.480777, 0.700074}, +{-0.57229, -0.460331, -0.67866}, +{0.570791, 0.71101, -0.410685}, +{-0.376382, 0.828, -0.415634}, +{-0.921773, -0.374459, 0.100576}, +{0.495866, -0.181019, 0.849322}, +{0.490444, -0.00188075, 0.871471}, +{-0.872174, -0.315783, -0.373623}, +{-0.395047, 0.872859, -0.286452}, +{-0.548521, 0.769207, -0.327789}, +{0.561721, 0.826992, -0.023511}, +{-0.118127, -0.28615, -0.950875}, +{0.986498, 0.150675, -0.0641796}, +{0.0918252, -0.934555, 0.343767}, +{-0.0928584, 0.440774, 0.892802}, +{0.930205, -0.152155, -0.334018}, +{-0.588075, 0.38693, 0.710248}, +{0.918815, -0.370693, 0.135521}, +{0.762426, -0.524987, -0.378279}, +{0.0127153, 0.750318, -0.660954}, +{-0.820224, -0.0701893, 0.56772}, +{-0.378097, 0.275756, -0.883743}, +{-0.84762, -0.325487, 0.419044}, +{0.811966, 0.549029, -0.198189}, +{0.794764, -0.209259, 0.569703}, +{-0.184438, -0.797585, -0.574318}, +{0.705616, -0.612879, -0.355647}, +{-0.232908, -0.0979754, 0.967551}, +{0.566753, -0.204169, -0.798189}, +{-0.421614, 0.621579, 0.660214}, +{-0.73206, 0.102491, 0.673486}, +{0.546562, 0.00251961, 0.837415}, +{-0.0432573, 0.661303, 0.74887}, +{-0.0630739, -0.452173, 0.889697}, +{-0.587725, -0.260512, -0.765971}, +{0.961765, 0.00292025, -0.273859}, +{-0.538014, -0.147449, 0.82994}, +{-0.298212, 0.254243, -0.920016}, +{-0.639355, 0.368771, -0.67471}, +{0.25556, 0.0358078, -0.96613}, +{-0.342135, -0.0838824, 0.935899}, +{-0.764652, 0.103189, 0.636129}, +{0.554393, 0.424371, -0.715931}, +{0.184816, -0.39865, 0.898288}, +{-0.911218, 0.0874296, 0.402539}, +{0.721785, -0.356053, -0.593509}, +{0.834466, 0.260504, -0.485598}, +{-0.669452, 0.742262, -0.0296849}, +{-0.562414, 0.215582, -0.798257}, +{-0.388226, 0.185776, -0.902645}, +{0.280494, 0.850822, 0.444326}, +{-0.22455, -0.943729, 0.242803}, +{0.798238, -0.401392, -0.449111}, +{0.922097, 0.230165, -0.311064}, +{0.557576, 0.697959, 0.449403}, +{0.179823, -0.970894, 0.158205}, +{-0.64033, -0.507953, 0.57616}, +{-0.47583, 0.0516727, 0.878018}, +{-0.882236, 0.442312, 0.161306}, +{0.0858194, -0.752397, 0.653095}, +{0.130744, -0.651422, 0.747366}, +{-0.00733291, -0.587168, 0.809432}, +{-0.870417, 0.341242, 0.354864}, +{0.500817, 0.466707, -0.728949}, +{-0.616696, -0.726973, -0.301988}, +{-0.181508, -0.453847, -0.872398}, +{0.946023, -0.316608, 0.0692826}, +{0.311464, -0.297936, 0.902344}, +{0.23938, 0.837971, 0.49041}, +{-0.674039, -0.729612, -0.115492}, +{0.922931, 0.169901, 0.345444}, +{0.17907, -0.197022, -0.963907}, +{0.782485, 0.591237, -0.195337}, +{0.507399, 0.859585, 0.0605002}, +{-0.589234, -0.731746, 0.342564}, +{-0.713209, 0.510142, -0.480716}, +{-0.577928, -0.196155, 0.792163}, +{0.870836, -0.228715, 0.435125}, +{-0.0664052, 0.993473, 0.0927448}, +{0.959015, 0.277223, -0.0586296}, +{-0.447561, 0.223451, 0.865886}, +{0.230264, -0.910599, -0.343203}, +{-0.719232, 0.670808, -0.180888}, +{0.549912, 0.629191, -0.549286}, +{0.687342, 0.620961, -0.376787}, +{0.383132, 0.302502, -0.872756}, +{0.776146, -0.339611, 0.531284}, +{-0.679159, 0.170699, 0.713866}, +{0.738795, 0.608519, -0.289632}, +{0.313729, -0.844494, -0.434055}, +{-0.220112, 0.871649, -0.437926}, +{0.555705, -0.812149, -0.177782}, +{0.0112816, -0.102011, 0.994719}, +{-0.345411, -0.180906, -0.92085}, +{-0.174787, 0.750139, -0.637762}, +{-0.528849, 0.446578, -0.721725}, +{0.592541, 0.123276, 0.796051}, +{0.381792, 0.548896, 0.743605}, +{-0.761683, -0.60261, -0.238117}, +{-0.343907, -0.910046, 0.231398}, +{-0.792326, 0.217923, 0.569851}, +{-0.632527, 0.733766, -0.247986}, +{0.774706, 0.133392, -0.618092}, +{0.522803, 0.782921, -0.337211}, +{0.796652, 0.0291944, -0.603733}, +{-0.618471, 0.694867, 0.36695}, +{0.77107, 0.461472, -0.438742}, +{0.988064, 0.110682, 0.107143}, +{-0.842061, -0.510938, -0.172846}, +{-0.129952, -0.482301, 0.866313}, +{-0.675298, 0.366019, 0.640315}, +{-0.417112, 0.476997, 0.773622}, +{0.155813, -0.0265306, 0.98743}, +{0.798572, -0.548248, 0.248407}, +{-0.153654, -0.94499, 0.288763}, +{0.18851, -0.980011, -0.0635725}, +{0.148146, -0.987498, 0.0538528}, +{-0.64539, 0.576885, 0.500675}, +{-0.330543, 0.692125, -0.641642}, +{0.354902, 0.429512, 0.8304}, +{-0.813098, 0.452218, -0.366566}, +{0.633344, -0.317279, 0.705839}, +{-0.0333403, -0.91423, -0.403821}, +{0.870271, 0.134948, -0.473727}, +{0.481163, 0.864814, -0.143452}, +{-0.798512, 0.537712, -0.270635}, +{0.75664, 0.00341982, 0.653822}, +{-0.425424, -0.598962, -0.678424}, +{-0.726417, 0.462847, 0.508027}, +{0.970769, -0.238527, 0.0267021}, +{-0.90008, 0.268152, -0.343439}, +{0.440394, -0.891815, -0.103539}, +{0.940944, -0.146349, -0.305298}, +{-0.871284, 0.481859, -0.0931483}, +{0.613643, -0.288044, 0.735169}, +{-0.579423, 0.808823, -0.100371}, +{0.67157, -0.701429, 0.238729}, +{-0.889215, -0.33559, 0.310929}, +{-0.595047, -0.779682, -0.194974}, +{-0.765451, 0.53547, 0.356871}, +{-0.60529, 0.71386, -0.352175}, +{-0.752973, 0.241486, 0.612141}, +{0.577529, -0.670834, -0.465234}, +{0.901541, 0.214379, 0.375854}, +{0.833721, -0.216135, 0.508129}, +{0.128911, -0.0673679, -0.989365}, +{0.322106, 0.235088, -0.917051}, +{-0.595395, 0.107992, 0.796142}, +{0.318106, 0.441336, 0.839066}, +{-0.593565, -0.684176, 0.423773}, +{-0.413223, 0.782086, -0.466463}, +{0.305657, -0.322528, -0.895851}, +{-0.368517, 0.881428, 0.295433}, +{-0.181184, 0.927436, 0.327162}, +{-0.282772, 0.955543, 0.0835317}, +{0.960188, -0.278661, -0.0196872}, +{-0.800918, 0.327066, -0.501556}, +{0.626262, 0.0123683, -0.779514}, +{-0.832885, 0.540322, -0.119809}, +{0.90119, 0.400752, -0.165092}, +{-0.378266, 0.920926, 0.0938635}, +{0.0473079, -0.416434, 0.907934}, +{0.942935, 0.321005, 0.0884819}, +{-0.0999173, -0.157577, -0.982439}, +{0.318958, -0.2874, 0.903143}, +{0.0460037, -0.259299, 0.964701}, +{-0.253818, -0.365588, -0.895501}, +{-0.105723, -0.118997, -0.98725}, +{0.0760366, -0.656088, 0.750844}, +{-0.968054, -0.247461, -0.0404303}, +{-0.598547, 0.708427, -0.373996}, +{0.42037, 0.369042, 0.828913}, +{-0.935184, -0.158851, -0.316541}, +{0.39322, -0.791305, 0.468204}, +{-0.433578, 0.661417, 0.611995}, +{-0.456742, 0.755348, -0.469932}, +{-0.261466, -0.813283, -0.519813}, +{0.615299, -0.17416, -0.768814}, +{-0.953213, -0.0770189, -0.292324}, +{0.798591, 0.522097, -0.299445}, +{0.0243203, 0.00176753, 0.999703}, +{-0.239502, 0.455102, 0.857625}, +{-0.165432, -0.393871, -0.904156}, +{0.396429, -0.0617446, -0.915987}, +{0.15597, 0.861777, -0.482714}, +{0.375452, -0.92554, -0.0491122}, +{0.317093, -0.947518, 0.0407687}, +{-0.813502, -0.491994, 0.31009}, +{0.0445713, -0.814496, 0.578454}, +{0.525339, 0.0638176, -0.848497}, +{-0.52823, -0.471064, -0.70645}, +{0.932197, 0.0339905, -0.360352}, +{-0.165969, -0.331303, -0.928813}, +{-0.654864, -0.711443, -0.254953}, +{-0.0502693, -0.188867, -0.980715}, +{-0.627421, -0.0463894, 0.777297}, +{0.107303, 0.992174, 0.0638509}, +{0.838842, -0.0606279, 0.540989}, +{-0.315994, -0.738822, 0.595223}, +{0.725319, 0.44671, 0.523796}, +{0.588816, -0.348738, 0.729162}, +{-0.48709, 0.422633, -0.76428}, +{0.745159, -0.629666, 0.219679}, +{-0.706558, -0.552886, 0.441693}, +{-0.0339055, 0.980812, -0.191985}, +{0.0512863, 0.421878, 0.905201}, +{0.326822, 0.86577, 0.378986}, +{0.661461, -0.515112, 0.545096}, +{0.524294, -0.104427, 0.84511}, +{0.428149, -0.498237, 0.753955}, +{0.69557, 0.718083, 0.0232217}, +{0.719113, -0.694867, 0.00591781}, +{0.318743, 0.774906, -0.545824}, +{-0.19302, -0.959042, 0.207322}, +{-0.592197, 0.803931, -0.0547534}, +{0.303717, 0.0771767, -0.949631}, +{-0.374034, 0.240094, 0.895797}, +{-0.880081, 0.26221, 0.395857}, +{0.116794, -0.858554, -0.499244}, +{0.909291, -0.380895, -0.167656}, +{0.135535, -0.862324, 0.487881}, +{-0.676074, 0.736616, -0.017916}, +{-0.540387, 0.0401064, 0.84046}, +{-0.0165481, 0.681485, 0.731645}, +{0.135539, 0.904741, -0.403822}, +{-0.70663, -0.662078, 0.249651}, +{-0.277946, -0.551942, 0.786197}, +{-0.252472, 0.936569, 0.243099}, +{0.212202, 0.890011, -0.403548}, +{0.726276, -0.0147126, -0.687245}, +{-0.507034, -0.819933, -0.265758}, +{-0.833989, 0.389232, -0.391103}, +{-0.443344, -0.687612, 0.575009}, +{0.836122, 0.103498, 0.538692}, +{-0.874235, 0.179487, 0.451108}, +{0.236486, -0.248905, 0.939213}, +{0.404582, 0.271584, 0.873244}, +{-0.458956, 0.789618, 0.407263}, +{-0.645396, 0.170449, 0.744588}, +{0.46963, 0.817569, -0.333209}, +{-0.642875, -0.728478, 0.236711}, +{0.19651, -0.816261, -0.543233}, +{-0.837083, -0.0246678, -0.54652}, +{0.94096, 0.100962, -0.323112}, +{0.253911, -0.709579, 0.657288}, +{0.585565, 0.788747, -0.187062}, +{0.372048, 0.296242, -0.879671}, +{-0.822661, 0.261713, 0.504713}, +{-0.396345, 0.512504, -0.761742}, +{0.857031, 0.51117, 0.0648367}, +{0.640934, -0.757898, 0.121631}, +{-0.210667, 0.966713, 0.145206}, +{-0.306418, -0.547491, -0.778692}, +{-0.756442, 0.150816, -0.636435}, +{0.486667, 0.871331, 0.0627408}, +{-0.863612, 0.469728, -0.183109}, +{0.870743, -0.484281, 0.0853165}, +{-0.228072, 0.971784, 0.0601495}, +{-0.725886, 0.538276, -0.428191}, +{-0.453304, 0.0769717, -0.888026}, +{-0.752347, -0.346085, -0.560535}, +{-0.707147, -0.0358043, -0.706159}, +{0.131625, 0.497126, 0.857636}, +{0.363628, 0.0447506, -0.930469}, +{-0.687752, 0.374049, 0.622161}, +{0.513926, -0.109022, -0.850879}, +{0.938769, -0.326917, 0.108804}, +{0.548046, 0.832461, 0.0815677}, +{-0.47875, 0.831703, -0.281193}, +{-0.978368, -0.153476, -0.138716}, +{0.0399427, 0.409297, 0.911526}, +{-0.600716, -0.442912, 0.66556}, +{0.192134, 0.299201, 0.934646}, +{0.511019, 0.499389, 0.699621}, +{-0.173113, -0.708839, 0.683798}, +{-0.425179, -0.184498, 0.886106}, +{-0.711214, 0.244916, -0.658931}, +{0.359844, 0.720022, 0.593364}, +{0.159352, -0.355233, 0.921095}, +{-0.132973, -0.974565, -0.180391}, +{0.875414, -0.0913639, 0.47466}, +{-0.597893, 0.160109, -0.785423}, +{0.364605, -0.90819, 0.205556}, +{0.06045, 0.45557, 0.888145}, +{0.540111, -0.668289, -0.511536}, +{0.964069, -0.244494, 0.10389}, +{0.629514, 0.0618933, -0.77452}, +{0.946418, -0.0399812, -0.320461}, +{-0.920376, -0.257562, 0.294228}, +{-0.735146, -0.513263, 0.442856}, +{0.590205, -0.788457, 0.173184}, +{-0.353274, -0.930822, 0.0936396}, +{0.164589, 0.044224, 0.98537}, +{0.578352, -0.772022, 0.263611}, +{0.282666, 0.886142, -0.367223}, +{-0.966073, -0.224526, -0.127637}, +{-0.192898, 0.649861, -0.735167}, +{-0.571889, 0.390192, -0.721591}, +{0.569665, 0.781625, -0.254055}, +{-0.870498, -0.120377, 0.477224}, +{0.101627, -0.814895, -0.570629}, +{0.994193, -0.107607, 0.00111603}, +{-0.394179, -0.334274, -0.856086}, +{0.142877, -0.972517, 0.18384}, +{-0.85806, 0.432953, -0.276196}, +{0.157665, 0.229335, 0.960493}, +{-0.135365, -0.971377, -0.195202}, +{0.413552, 0.864748, 0.284931}, +{0.842791, -0.52168, 0.132491}, +{-0.823184, -0.246695, -0.51138}, +{-0.943891, 0.330142, 0.00869059}, +{-0.973575, 0.0822292, 0.213049}, +{-0.776558, -0.611614, -0.151281}, +{-0.506637, -0.485734, -0.712307}, +{0.338201, -0.924644, -0.175081}, +{0.613952, -0.613534, 0.496628}, +{0.402849, -0.228036, -0.886404}, +{0.336843, 0.895443, 0.291065}, +{0.728775, 0.552289, -0.404801}, +{-0.780533, -0.415655, 0.466903}, +{0.765954, -0.633163, -0.11144}, +{-0.545607, 0.819645, 0.174629}, +{0.0178708, -0.607655, -0.794}, +{-0.91995, 0.0994564, -0.379211}, +{0.347758, -0.793667, -0.499156}, +{-0.133425, 0.768088, -0.62629}, +{-0.957524, -0.225799, 0.179339}, +{0.591333, -0.247424, -0.767533}, +{0.147433, -0.674425, 0.723473}, +{-0.0392865, 0.849273, 0.52649}, +{0.412295, 0.392127, -0.822344}, +{-0.0520864, 0.0437563, 0.997684}, +{0.901821, 0.415307, -0.119325}, +{-0.0698159, -0.996132, -0.0533587}, +{0.913715, 0.398909, -0.077436}, +{-0.895175, 0.378432, 0.235481}, +{0.44407, 0.876406, 0.186315}, +{0.897025, 0.00745651, 0.441918}, +{-0.241982, 0.758671, -0.604866}, +{0.444457, 0.71196, 0.543664}, +{0.861931, -0.49813, -0.0945605}, +{0.633682, 0.77033, -0.0709791}, +{-0.788784, -0.613126, -0.0435511}, +{0.679393, -0.215021, 0.701563}, +{-0.498649, 0.476058, 0.724374}, +{0.0845595, 0.642359, 0.761724}, +{0.610932, 0.694006, 0.380943}, +{-0.562279, -0.393826, 0.727148}, +{-0.309404, -0.488311, 0.815979}, +{0.885899, 0.0837269, 0.45626}, +{-0.0971775, -0.363921, -0.926347}, +{0.409523, 0.889623, -0.202143}, +{0.311584, -0.57582, -0.755875}, +{0.587849, -0.802894, 0.0989699}, +{0.795965, -0.532648, 0.287621}, +{-0.846199, -0.189443, 0.498054}, +{0.695243, -0.652536, 0.301387}, +{0.628055, 0.274577, 0.728117}, +{-0.576019, 0.688255, -0.44103}, +{0.738818, 0.54163, 0.400981}, +{0.664963, 0.321857, 0.673968}, +{0.0624466, 0.411091, -0.909453}, +{0.530934, -0.555077, -0.640312}, +{-0.384098, 0.420588, -0.821933}, +{0.0875831, 0.72359, -0.68465}, +{-0.994236, 0.0967291, 0.046251}, +{0.146737, -0.806988, -0.572048}, +{0.137215, -0.273842, -0.951936}, +{0.836988, 0.53746, -0.1029}, +{0.320899, -0.0780192, -0.943894}, +{-0.555955, -0.815488, -0.160916}, +{-0.300561, 0.8539, 0.424874}, +{0.65946, 0.700367, 0.273128}, +{0.168787, -0.922117, 0.348154}, +{0.981677, 0.0347978, 0.187347}, +{0.439493, 0.0806838, -0.894615}, +{-0.941232, 0.0338837, -0.336056}, +{0.902056, 0.240554, -0.358368}, +{-0.532769, -0.00974262, 0.846205}, +{0.313342, -0.741408, -0.593407}, +{0.772939, -0.130526, -0.620909}, +{0.351409, 0.628187, -0.694185}, +{-0.613224, -0.278286, 0.739265}, +{0.493252, 0.461993, -0.737065}, +{0.468243, 0.351733, -0.810575}, +{-0.121265, 0.967492, 0.221934}, +{-0.0418965, 0.997999, 0.0473483}, +{0.714151, 0.419431, -0.560416}, +{0.92816, 0.273364, -0.252568}, +{-0.387882, -0.0986398, 0.916416}, +{0.454915, 0.82352, 0.33892}, +{0.939215, -0.29534, -0.175071}, +{-0.110855, 0.350433, 0.930004}, +{-0.795868, -0.603359, 0.0505148}, +{0.437998, -0.104108, -0.892927}, +{0.660449, -0.290846, -0.692254}, +{0.707355, 0.678726, 0.197432}, +{0.187352, -0.953774, -0.234978}, +{0.236054, 0.888549, -0.393395}, +{-0.562473, -0.687097, 0.459915}, +{0.116649, -0.905524, -0.407946}, +{-0.00375796, -0.954848, -0.297071}, +{0.969646, 0.234324, -0.0698487}, +{0.611054, -0.706184, -0.357655}, +{-0.405953, 0.648746, -0.643686}, +{-0.382589, -0.489329, 0.783698}, +{0.720065, -0.239119, 0.651405}, +{0.532524, 0.841165, 0.0941273}, +{0.629624, -0.741271, -0.232576}, +{0.636311, 0.4287, -0.641346}, +{0.752229, 0.44629, -0.484744}, +{-0.447495, 0.866523, 0.221102}, +{-0.807279, -0.0925687, -0.582865}, +{-0.202069, -0.921704, -0.331104}, +{-0.0879049, -0.702763, -0.705972}, +{-0.416006, -0.83348, -0.363663}, +{0.149587, -0.950857, -0.271099}, +{0.58252, 0.797993, -0.154526}, +{0.185037, -0.881507, -0.434404}, +{-0.186892, 0.576758, -0.795249}, +{0.768275, -0.31828, 0.555385}, +{-0.341624, -0.638628, -0.689527}, +{-0.481821, -0.557437, 0.676101}, +{0.884766, -0.382344, 0.266464}, +{0.162069, -0.911581, 0.377827}, +{0.634314, 0.0535449, 0.771219}, +{-0.453231, -0.877817, 0.154979}, +{-0.587283, 0.64944, -0.483038}, +{-0.786874, -0.247218, 0.565432}, +{-0.742553, -0.657052, 0.129992}, +{-0.305286, -0.337529, -0.890435}, +{0.0222397, 0.396906, -0.91759}, +{-0.738873, 0.622373, -0.258301}, +{0.324064, 0.367792, -0.871614}, +{0.171476, 0.590216, -0.788823}, +{-0.818034, -0.111902, 0.564179}, +{-0.443639, -0.762316, -0.471231}, +{-0.432283, 0.834107, 0.342631}, +{0.548462, 0.740889, -0.387651}, +{0.523562, 0.116192, 0.844027}, +{-0.497542, 0.660264, -0.562586}, +{0.693397, 0.67742, 0.245564}, +{-0.554347, 0.34331, 0.75818}, +{-0.663835, -0.676708, 0.318417}, +{0.340256, 0.919351, -0.197534}, +{0.221527, -0.292645, 0.930207}, +{-0.230042, -0.639, 0.734002}, +{0.875182, -0.0870732, 0.475894}, +{0.62755, 0.717855, 0.301438}, +{0.609316, -0.0899034, 0.787815}, +{0.0483973, 0.401984, 0.914367}, +{0.191491, -0.8733, 0.447971}, +{0.947633, -0.0344134, 0.317502}, +{-0.940824, 0.111838, 0.319911}, +{0.534045, -0.396984, 0.746458}, +{0.968332, 0.0266654, 0.248238}, +{0.0378821, 0.956607, -0.288909}, +{0.516663, 0.856179, -0.00401514}, +{0.429393, 0.901735, -0.0499631}, +{-0.638202, 0.713038, -0.290301}, +{0.544462, -0.449368, -0.708258}, +{-0.877041, -0.303294, 0.372575}, +{-0.771237, 0.57971, -0.262925}, +{-0.556925, -0.812225, -0.173563}, +{-0.138957, -0.566267, 0.812424}, +{-0.46577, -0.492698, 0.735056}, +{-0.152617, 0.922742, -0.353915}, +{-0.444039, 0.380425, -0.811237}, +{0.356378, 0.788901, -0.500629}, +{-0.324088, -0.218873, 0.920359}, +{-0.150415, -0.852808, -0.500095}, +{-0.962593, -0.176461, -0.205613}, +{0.64903, -0.528772, 0.546955}, +{-0.701458, -0.495026, -0.512743}, +{-0.202469, -0.0319239, -0.978768}, +{-0.818841, 0.537429, 0.201667}, +{-0.479134, -0.361506, 0.79984}, +{-0.524509, 0.268457, -0.807974}, +{-0.702082, 0.693575, 0.161352}, +{0.348675, 0.852137, -0.390242}, +{0.0966414, 0.318672, -0.942926}, +{-0.617737, 0.672782, -0.407143}, +{0.296083, 0.709182, 0.639841}, +{0.625446, 0.731504, -0.271512}, +{-0.51068, -0.364191, -0.778827}, +{-0.818396, -0.414511, -0.398006}, +{-0.106025, 0.750162, 0.652699}, +{-0.916904, -0.358079, 0.176257}, +{-0.0762473, -0.988151, -0.133203}, +{-0.333817, 0.697019, -0.634611}, +{0.678003, 0.729539, -0.0899111}, +{0.333797, 0.661557, -0.671507}, +{-0.379749, 0.913583, 0.145454}, +{0.23032, 0.696227, -0.679868}, +{0.323088, -0.538924, -0.77793}, +{0.111239, 0.341127, 0.933412}, +{0.877555, 0.461567, 0.129819}, +{-0.906009, 0.340158, -0.251873}, +{-0.34641, -0.391311, -0.85257}, +{-0.600364, -0.170214, 0.781403}, +{-0.810065, -0.43604, 0.392001}, +{0.338285, 0.762735, 0.551178}, +{-0.841061, 0.540441, 0.0232163}, +{-0.712011, 0.15868, -0.684003}, +{-0.804979, 0.391932, 0.445418}, +{-0.644414, -0.075504, 0.76094}, +{0.353098, 0.124598, 0.927252}, +{0.575693, 0.184434, -0.796594}, +{-0.618925, -0.426639, -0.659477}, +{0.72232, -0.00454775, 0.691544}, +{-0.493034, -0.674739, -0.549222}, +{-0.84141, 0.236133, 0.486077}, +{-0.730307, -0.468941, 0.496736}, +{-0.847003, 0.143774, 0.511777}, +{0.219405, 0.770731, 0.598193}, +{-0.262571, 0.905015, 0.334669}, +{0.458735, -0.67818, 0.574137}, +{-0.420987, -0.119881, -0.89911}, +{0.00494382, -0.159686, 0.987155}, +{-0.654871, 0.702545, 0.278521}, +{-0.806687, 0.169039, 0.566287}, +{-0.710681, 0.114586, 0.69412}, +{-0.0273517, 0.00150442, 0.999625}, +{-0.717627, 0.521908, 0.461111}, +{0.766014, -0.051304, -0.640774}, +{-0.478723, 0.514035, 0.711753}, +{-0.136849, -0.316753, 0.938584}, +{0.0606409, -0.933425, 0.353611}, +{0.970236, -0.169979, 0.172478}, +{-0.889594, -0.392702, 0.233253} +}; + +assert(points.size() == 4000); +assert(points.back() == math.vec3f(-0.889594, -0.392702, 0.233253)); +assert(points.front() == math.vec3f(0.773228, -0.0707359, 0.63017)); + diff --git a/src/bin/mu/mu-interp/test/array_time.mu b/src/bin/mu/mu-interp/test/array_time.mu new file mode 100644 index 000000000..a381fb5ba --- /dev/null +++ b/src/bin/mu/mu-interp/test/array_time.mu @@ -0,0 +1,29 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// The first program uses arrays as if they were indexed. It sets n +// elements of an array to integer values, then copies the array to +// another array beginning at index n-1. +// + +int n = 200000; + +// int[] x, y; +// x.resize(n); +// y.resize(n); + +int[200000] x, y; + +for (int i = 0; i < n; i++) +{ + x[i] = i; +} + +for (int j = n-1; j >= 0; j--) +{ + y[j] = x[j]; +} diff --git a/src/bin/mu/mu-interp/test/ast_order.mu b/src/bin/mu/mu-interp/test/ast_order.mu new file mode 100644 index 000000000..35de2d39d --- /dev/null +++ b/src/bin/mu/mu-interp/test/ast_order.mu @@ -0,0 +1,157 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +\: add5 (int; int a, int b) { a + b; } + +class: X +{ + method: f (int;) { this.g(); } + method: g (int;) { add(911, 0); } +} + +class: Y +{ + method: pr(void;) { for_each (i; ints()) print ("%s\n" % i); } + method: ints(int[];) { int[] {1,2,3}; } +} + +class: Z +{ + method: Z (Z; int _a) { a=_a; } + int a; +} + +class: Base +{ + method: f (int; string a) { return int(a); } + method: f (int; int a, int b) { this.g(a) + b; } + method: g (int; int a) { a; } + method: p ((int;int);) { g; } +} + +class: Derived : Base +{ + method: f (int; int a, int b) { g(a) - b; } + method: g (int; int a) { a + 888; } + method: p ((int;int);) { f(7,); } +} + +\: bdtest (void;) +{ + Base b = Derived(); + let F = b.p(); + F(77); +} + +bdtest(); + + +{ + class: foo + { + [int] list; + + method: F (void;) { list = G() : list; } + method: G (int;) { 1; } + } + + let f = foo(); + f.F(); + f.F(); + //print("%s\n" % f); + // should be foo {[1,1]} +} + + +// not working yet +// let q = add(1,2); + +// not yet +// \: F (int;) +// { +// let (a,b,c) = tadd(1,2); +// c; +// } + +\: tadd ((int,int,int); int a, int b) { (a,b,add(a,b)); } + +\: add (int; int a, int b) { add1(a, b); } +\: add1 (int; int a, int b) { add2(a, b); } +\: add3 (int; int a, int b) { add4(a, b); } +\: add2 (int; int a, int b) { add3(a, b); } +\: add4 (int; int a, int b) { add5(a, b); } + + +// not yet (try this one first) +//assert(add(1,2) == F()); +assert(add(1,2) == 3); +assert(X().f() == 911); + +\: foo (int; int a) +{ + let x = bar(a, 10), + y = float(bar(a, 11)); + x + y; +} + +\: bar (int; int a, int b) { a + b; } + +assert(foo(1) == 23); + +\: F_each (void;) +{ + for_each (i; listtest()); +} + +\: F_index (void;) +{ + for_each (i; arraytest()); +} + +\: listtest ([int];) { [l1(), l2(), l3()]; } +\: tupletest ((int, int, int);) { (l1(), l2(), l3()); } +\: arraytest (int[];) { int[] {1,2,3}; } + +// crashes: +// \: tupletest () { (l1(), l2(), l3()); } + +\: l1 (int;) { 1; } +\: l2 (int;) { 2; } +\: l3 (int;) { 3; } + +{ + let [a, b, c] = listtest(), + (d, e, f) = tupletest(); + assert(a == 1 && a == d); + assert(b == 2 && b == e); + assert(c == 3 && c == f); +} + +// +// Method short cut. set() is called as Foo.set(this, n). +// + +class: Foo +{ + method: Foo (Foo; Foo n) { set(n); } + method: set (void; Foo n) { next = n; } + + Foo next; +}; + +Foo(Foo(nil)); + + +class: Rec +{ + Rec _next; + + method: next (Rec;) + { + if (_next eq nil) _next = Rec(Rec(nil)); + return _next; + } +} + diff --git a/src/bin/mu/mu-interp/test/bad_method.mu b/src/bin/mu/mu-interp/test/bad_method.mu new file mode 100644 index 000000000..020fa144a --- /dev/null +++ b/src/bin/mu/mu-interp/test/bad_method.mu @@ -0,0 +1,17 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +try +{ + string o = nil; + o.size(); +} +catch (exception e) +{ + print("caught: "); + print(e); + print("\n"); +} diff --git a/src/bin/mu/mu-interp/test/basic_float.mu b/src/bin/mu/mu-interp/test/basic_float.mu new file mode 100644 index 000000000..0d744e40e --- /dev/null +++ b/src/bin/mu/mu-interp/test/basic_float.mu @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +math.max((.23 + 345.E-2) / 80.0E0,3.1415e0 / 2.) diff --git a/src/bin/mu/mu-interp/test/basic_int.mu b/src/bin/mu/mu-interp/test/basic_int.mu new file mode 100644 index 000000000..574437b12 --- /dev/null +++ b/src/bin/mu/mu-interp/test/basic_int.mu @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +-((1+2)*6-10)*100/3%200 diff --git a/src/bin/mu/mu-interp/test/bbramp.mu b/src/bin/mu/mu-interp/test/bbramp.mu new file mode 100644 index 000000000..df4c4884c --- /dev/null +++ b/src/bin/mu/mu-interp/test/bbramp.mu @@ -0,0 +1,52 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// This is a simple test of the + +use math; +use math_util; + +Point := vector float[3]; +Vec := vector float[3]; +Color := vector float[3]; +dt := 0.041666666666; +ColorTemp := (float, Color); + +global ColorTemp[] bbColors = +{ + { 0.0, Color(0, 0, 0) }, + { 0.37, Color(0.937, 0, 0) }, + { 0.54, Color(1.0, 0.392, 0) }, + { 0.705, Color(1.0, 0.639, 0.146) }, + { 0.87, Color(1.0, 0.835, 0.612) }, + { 1.0, Color(1.0, 0.981, 0.943) }, + { 1.0, Color(1.0, 0.981, 0.943) } +}; + +\: blackBodyColor (Color; float u) +{ + Color rcol; + + for (int i=0, s=bbColors.size() - 1; i < s; i++) + { + let bb1 = bbColors[i+1], + u1 = bb1._0; + + if (u <= u1) + { + let bb0 = bbColors[i], + u0 = bb0._0, + c0 = bb0._1, + c1 = bb1._1; + + return lerp(c0, c1, linstep(u0, u1, u)); + } + } + + return Color(0); +} + +blackBodyColor(.5); diff --git a/src/bin/mu/mu-interp/test/benchmark.cc b/src/bin/mu/mu-interp/test/benchmark.cc new file mode 100644 index 000000000..891e1f9a2 --- /dev/null +++ b/src/bin/mu/mu-interp/test/benchmark.cc @@ -0,0 +1,28 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include + +// +// This file has to be backwards compatible with the original sophia +// or mel +// + +int main(int, char**) +{ + +int i,j; +float q=0; + +for (j=0; j<100; j++) +for (i=0; i<1000000; i++) +{ + q += (float(i) * ((i % 2)==0 ? -1. : 1.))/1000000.0; +} + +printf("%f\n",q); + + return 0; +} diff --git a/src/bin/mu/mu-interp/test/benchmark.mu b/src/bin/mu/mu-interp/test/benchmark.mu new file mode 100644 index 000000000..991dc72a1 --- /dev/null +++ b/src/bin/mu/mu-interp/test/benchmark.mu @@ -0,0 +1,37 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// This file has to be backwards compatible with the original sophia +// or mel +// + +// old: 170u 1.3s 2:58 +// new: 124u 0.99s 2:10 +// new_no_setjmp: 100u 0.6s 1:45 +// safe_no_setjmp: 173u 1.1s 3:00 + +// not safe, setjmp 122.730u 1.030s 2:10.65 94.7% 0+0k 0+5io 0pf+0w + +// latest opt: 117.350u 0.190s 1:59.51 98.3% 0+0k 0+0io 0pf+0w + +int i,j; +float q=0; + +for (j=0; j<100; j++) +for (i=0; i<1000000; i++) +{ + if ((i % 2)==0) + { + q += (float(i) * ( -1. ))/1000000.0; + } + else + { + q += (float(i) * ( 1.))/1000000.0; + } +} + +print(q); diff --git a/src/bin/mu/mu-interp/test/benchmark.python b/src/bin/mu/mu-interp/test/benchmark.python new file mode 100644 index 000000000..998a975f4 --- /dev/null +++ b/src/bin/mu/mu-interp/test/benchmark.python @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +q = 0.0; + +for j in range(0,100): + for i in range(0,10000000): + x = i % 2 + if x == 0: + q += (i * -1.0) / 1000000.0 + else: + q += (i * 1.0) / 1000000.0 + +print(q); diff --git a/src/bin/mu/mu-interp/test/bitops.mu b/src/bin/mu/mu-interp/test/bitops.mu new file mode 100644 index 000000000..72f2f4cf4 --- /dev/null +++ b/src/bin/mu/mu-interp/test/bitops.mu @@ -0,0 +1,17 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +/// should print 1 + +int a = 1; +int b = 2; +int c = 3; + +assert((a & b) == 0); +assert((a | b) == c); +assert((a & ~b) == a); +assert((a ^ c) == b); + diff --git a/src/bin/mu/mu-interp/test/bom.mu b/src/bin/mu/mu-interp/test/bom.mu new file mode 100644 index 000000000..963df7e58 --- /dev/null +++ b/src/bin/mu/mu-interp/test/bom.mu @@ -0,0 +1,14 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// This file should start with a BOM (byte order mark) to test the +// parser. NOTE: you won't see this in vim or emacs because they both +// transparently handle this insanity. +// +// apologies for the bad chinese +// + +"在開始的文字是“BOM”不幸的" diff --git a/src/bin/mu/mu-interp/test/bool.mu b/src/bin/mu/mu-interp/test/bool.mu new file mode 100644 index 000000000..07bb6986b --- /dev/null +++ b/src/bin/mu/mu-interp/test/bool.mu @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +true diff --git a/src/bin/mu/mu-interp/test/break_and_continue.mu b/src/bin/mu/mu-interp/test/break_and_continue.mu new file mode 100644 index 000000000..6564c2095 --- /dev/null +++ b/src/bin/mu/mu-interp/test/break_and_continue.mu @@ -0,0 +1,56 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +int i; + +for (i=0; i<100; i++) +{ + if (i == 10) break; + assert(i != 10); +} + +assert(i == 10); + +for (i=0; i<100; i++) +{ + continue; + assert(false); +} + +assert(i == 100); + +while (i != 0) +{ + i--; + if (i == 10) break; +} + +assert(i == 10); + +while (i != 100) +{ + i++; + continue; + assert(false); +} + +assert(i == 100); + +do +{ + if (i == 10) break; + i--; +} while(i > 0); + +assert(i == 10); + +do +{ + i++; + continue; + assert(false); +} while(i < 100); + diff --git a/src/bin/mu/mu-interp/test/byte.mu b/src/bin/mu/mu-interp/test/byte.mu new file mode 100644 index 000000000..62cbdee14 --- /dev/null +++ b/src/bin/mu/mu-interp/test/byte.mu @@ -0,0 +1,12 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +byte b = 10; +int i = 123; +b = i; +b = b >> 1; +b = b | byte(2); +assert(b == byte(63)); diff --git a/src/bin/mu/mu-interp/test/case.mu b/src/bin/mu/mu-interp/test/case.mu new file mode 100644 index 000000000..793ceb9a4 --- /dev/null +++ b/src/bin/mu/mu-interp/test/case.mu @@ -0,0 +1,82 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +union: Foo +{ + A (int,Foo) + | B float + | None +} + +use Foo; + +\: testFoo (string; Foo x) +{ + case (x) + { + None -> { return("NONE"); } + A (321, None) -> { return("321 with a None"); } + A (321, _) -> { return("321!"); } + A (_, None) -> { return("A with a None"); } + A (_, B x) -> { return("A with a B x = %f" % x); } + A (_, A (_, B x)) -> { return("A with an A with a B x = %f" % x); } + A x -> { return("A x"); } + B 1.0 -> { return("B 1.0"); } + B 1.123 -> { return("B 1.123"); } + B x -> { return("B x = %s" % x); } + } +} + +let testValues = [ (B(1.123), "B 1.123"), + (B(1.12311), "B x = 1.12311"), + (A((32,B(3.333))), "A with a B x = 3.333000"), + (A((32,A((0, B(3.333))))), "A with an A with a B x = 3.333000"), + (A((321,None)), "321 with a None"), + (A(1,A(1,A(1,None))), "A x"), + (None, "NONE") ]; + +for_each (v; testValues) +{ + let (x, s) = v; + assert(testFoo(x) == s); +} + + +let q = "three"; + +\: stringTest (string; string q) +{ + case (q) + { + "one" -> { return("1"); } + "two" -> { return("2"); } + t -> { return("value = %s" % t); } + } +} + +\: intTest (string; int x) +{ + case (x) + { + -1 -> { return("minus one"); } + 0 -> { return("zero"); } + 1 -> { return("one"); } + 2 -> { return("two"); } + _ -> { return("whatever"); } + } + + return "bad"; +} + +assert(intTest(-1) == "minus one"); +assert(intTest(0) == "zero"); +assert(intTest(1) == "one"); +assert(intTest(2) == "two"); +assert(intTest(3) == "whatever"); +assert(intTest(-3) == "whatever"); +assert(stringTest("one") == "1"); +assert(stringTest("two") == "2"); +assert(stringTest("three") == "value = three"); diff --git a/src/bin/mu/mu-interp/test/cast.mu b/src/bin/mu/mu-interp/test/cast.mu new file mode 100644 index 000000000..147815976 --- /dev/null +++ b/src/bin/mu/mu-interp/test/cast.mu @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +int(3.14) diff --git a/src/bin/mu/mu-interp/test/char.mu b/src/bin/mu/mu-interp/test/char.mu new file mode 100644 index 000000000..a8964f2b3 --- /dev/null +++ b/src/bin/mu/mu-interp/test/char.mu @@ -0,0 +1,20 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +char f = 'f', a = 'a', b = 'b', c = 'c'; + +assert( f + 1 == 'g' ); +assert( f - 1 == 'e' ); +assert( f - 'a' == 5 ); + +assert( a < f && a <= f && f <= f && f >= a && f >= f ); +assert( a++ == 'a' ); +a--; +assert( ++a == b ); +a--; +assert( b-- == 'b' ); +b++; +assert( --b == a); diff --git a/src/bin/mu/mu-interp/test/class_operators.mu b/src/bin/mu/mu-interp/test/class_operators.mu new file mode 100644 index 000000000..eb83d93e3 --- /dev/null +++ b/src/bin/mu/mu-interp/test/class_operators.mu @@ -0,0 +1,16 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +class: Foo +{ + operator: [] (int; Foo this, int index) { return n[index]; } + operator: () (int; Foo this, int index) { return n[index]; } + int[] n; +}; + +Foo f = {{1,2,3}}; +assert(f[1] == 2); +assert(f(1) == 2); diff --git a/src/bin/mu/mu-interp/test/color_hash_table.mu b/src/bin/mu/mu-interp/test/color_hash_table.mu new file mode 100644 index 000000000..344a92523 --- /dev/null +++ b/src/bin/mu/mu-interp/test/color_hash_table.mu @@ -0,0 +1,96 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +module: color_hash_table +{ + // + // NOTE: we're defining color the same way rvui and gl modules + // do: an r g b triple + alpha. + // + + Color := vector float[4]; + + global int[] primes = + { 7, 13, 23, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, + 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, + 12582917, 25165843 }; + + \: nextPrime (int; int p) + { + for_index (i; primes) if (primes[i] > p) return primes[i]; + return primes.back(); + } + + \: hash (Color c) { string(c).hash(); } + + documentation: """ + A basic hash map which uses a color (R, G, B) as the index. The hash + is created by converting to a string and using the string.hash() function. + """; + + class: HashTable + { + class: Item + { + Color color; + string value; + Item next; + } + + Item[] _table; + int _numItems; + + method: _addInternal (void; Color color, string value) + { + let i = hash(color) % _table.size(); + _table[i] = Item(color, value, _table[i]); + _numItems++; + } + + method: resize (void;) + { + let newSize = nextPrime(_table.size()), + oldTable = _table; + + _table = Item[](); + _table.resize(newSize); + + for_each (item; oldTable) + { + for (Item i = item; i neq nil; i = i.next) + { + _addInternal(i.color, i.value); + } + } + } + + method: HashTable (HashTable; int initialSize) + { + _table = Item[](); + _table.resize(nextPrime(initialSize)); + resize(); + } + + method: add (void; Color color, string value) + { + _addInternal(color, value); + if (_numItems > _table.size() * 2) resize(); + } + + method: find (string; Color color) + { + let i = hash(color) % _table.size(); + + for (Item x = _table[i]; x neq nil; x = x.next) + { + if (x.color == color) return x.value; + } + + return nil; + } + } +} + diff --git a/src/bin/mu/mu-interp/test/colorhash.mu b/src/bin/mu/mu-interp/test/colorhash.mu new file mode 100644 index 000000000..ee2834949 --- /dev/null +++ b/src/bin/mu/mu-interp/test/colorhash.mu @@ -0,0 +1,68 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +use color_hash_table; + +HashTable table = HashTable(10); // initial expected size (can be approx) + +// +// Look up some specific colors and make sure the values match +// + +\: lookupValid (void; (Color,string)[] colors, HashTable table) +{ + for_each (c; colors) + { + let (color, value) = c, + v = table.find(color); + + assert(v == value); + //print("%s => %s\n" % (color, v)); + } +} + +// +// Some basic colors and names. +// + +let colors = (Color,string)[] +{ + (Color(1,0,0), "red"), + (Color(0,1,0), "green"), + (Color(0,0,1), "blue"), + (Color(0,1,1), "cyan"), + (Color(1,0,1), "magenta"), + (Color(1,1,0), "yellow") +}; + +// +// Add our colors and test to see if they are found properly +// + +for_each (c; colors) table.add(c._0, c._1); +lookupValid(colors, table); + +// +// Add 20000 random entries. Adding can be a bit slow since the table +// has to be rebuilt each time the load becomes too large. In this +// case the table will be reconstructed 10 times before the loop is +// complete. +// + +repeat (20000) +{ + use math_util; + let c = Color(random(1.0), random(1.0), random(1.0)), + v = string(c); + + table.add(c, v); +} + +// +// Test our colors again. The lookup should be fast. +// + +//print("-\n"); +lookupValid(colors, table); diff --git a/src/bin/mu/mu-interp/test/compose.mu b/src/bin/mu/mu-interp/test/compose.mu new file mode 100644 index 000000000..0dbd1b9ee --- /dev/null +++ b/src/bin/mu/mu-interp/test/compose.mu @@ -0,0 +1,42 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// Let's say I have x of type (float;float) and y of type +// (float;float,float) and I want z == y(x(_1),_2) +// +// NOTE: these examples purposefully avoid using closures to achieve +// the same effect. See free.mu for the same examples using closures. +// + +F1 := (float;float); +F2 := (float;float,float); + +\: compose (F1 a, F2 b) +{ + (\: (float x, float y, F1 a, F2 b) { b(a(x), y); })(,,a,b); +} + +// +// F should be: +// +// \: (float x, float y) { math.atan2(math.sin(x), y); } +// +// after function reduction. +// + +require math; +let F = compose(math.sin, math.atan2); +print(string(F) + "\n"); + +// +// This statement *should* reduce to: assert(true) since F is +// constant and additional arguments are also constant. Alternately, +// if F is bound to the anonymous function as a function symbol, it +// would be a straightforward call constant reduction. +// + +assert(F(1.0, 2.0) == math.atan2(math.sin(1.0), 2.0)); + diff --git a/src/bin/mu/mu-interp/test/compose_random.mu b/src/bin/mu/mu-interp/test/compose_random.mu new file mode 100644 index 000000000..7c1fa76ad --- /dev/null +++ b/src/bin/mu/mu-interp/test/compose_random.mu @@ -0,0 +1,51 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// This test tries to compose random mu expressions and then evaluate +// them. The expressions are limited to single and double argument +// + +use math_util; +use math; + +F1 := (float;float); +F2 := (float;float,float); + +global F1[] f1_funcs = +{ + math.sin, math.cos, math.tan, math.sqrt, + math.cbrt, math.asin, math.acos, math.atan, + (-) +}; + +global F2[] f2_funcs = +{ + math.pow, math.atan2, (-), (*), (/), (%) +}; + + +\: random_F1 () { f1_funcs[random(f1_funcs.size())]; } +\: random_F2 () { f2_funcs[random(f2_funcs.size())]; } + +\: compose_F1 (F1 f) +{ + \: (F1 b, float x) { f(b(x)); }; +} + +\: compose_F2 (F2 f) +{ + \: (F1 b, float x, float y) { f(b(x), y); }; +} + +\: random_reduce_F2 (F2 f) +{ + (\: (float x, float y) { f(x, y); })(random(1.0),); +} + +\: random_reduce_F1 (F1 f) +{ + f(random(1.0)); +} diff --git a/src/bin/mu/mu-interp/test/cond_expr.mu b/src/bin/mu/mu-interp/test/cond_expr.mu new file mode 100644 index 000000000..03887ae2c --- /dev/null +++ b/src/bin/mu/mu-interp/test/cond_expr.mu @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +assert( (if false then 1. else 2) == 2 ); diff --git a/src/bin/mu/mu-interp/test/constant_expr.mu b/src/bin/mu/mu-interp/test/constant_expr.mu new file mode 100644 index 000000000..0b61f7bc0 --- /dev/null +++ b/src/bin/mu/mu-interp/test/constant_expr.mu @@ -0,0 +1,11 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// This should all be reduced to the constant 2 +// + +assert(-1 + 2 + math.cos(0.) + math.sin(0) == 2); diff --git a/src/bin/mu/mu-interp/test/construct.mu b/src/bin/mu/mu-interp/test/construct.mu new file mode 100644 index 000000000..5a8b93c66 --- /dev/null +++ b/src/bin/mu/mu-interp/test/construct.mu @@ -0,0 +1,12 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +//int foo; + +//foo(1,4) +//{ + //int q = 51; +//} diff --git a/src/bin/mu/mu-interp/test/constructors.mu b/src/bin/mu/mu-interp/test/constructors.mu new file mode 100644 index 000000000..e32c13263 --- /dev/null +++ b/src/bin/mu/mu-interp/test/constructors.mu @@ -0,0 +1,40 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// Supply the constructors that normally are automatically +// generated. Make sure that the NodeAssembler deals with this +// situation in a sane way. +// + +class: Foo +{ + method: Foo (Foo; int _a, int _b) { a=_a; b=_b; } + method: Foo (Foo;) { a = 0; b = 0; } + int a; + int b; +} + +class: Bar +{ + int a; + int b; +} + +class: FooDerived : Foo +{ + method: FooDerived (FooDerived; int _a, int _b) + { + // call base class constructor explicitly + Foo.Foo(this, _a, _b); + } +} + +let a = Foo(1,2), + b = Foo(), + c = Bar(1,2), + d = Bar(); + +assert(FooDerived(9,8).b == 8); diff --git a/src/bin/mu/mu-interp/test/declare_dependent.mu b/src/bin/mu/mu-interp/test/declare_dependent.mu new file mode 100644 index 000000000..13f7b84de --- /dev/null +++ b/src/bin/mu/mu-interp/test/declare_dependent.mu @@ -0,0 +1,13 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +let a = 1, + b = a + 2; + +global let c = b, + d = c + a + b; + +assert(b == 3); +assert(d == 7); diff --git a/src/bin/mu/mu-interp/test/default_value.mu b/src/bin/mu/mu-interp/test/default_value.mu new file mode 100644 index 000000000..e96494d11 --- /dev/null +++ b/src/bin/mu/mu-interp/test/default_value.mu @@ -0,0 +1,36 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +function: puke (float; + int which_one, + float vomit = math.cos(math.pi) * -1.0, + float yak = 33.8, + float heave = 44 - 7) +{ + if (which_one == 1) return vomit; + if (which_one == 2) return yak; + if (which_one == 3) return heave; + + throw exception("ok"); +} + + +assert( puke(1) == math.cos(math.pi) * -1.0 ); +assert( puke(2) == 33.8 ); +assert( puke(3) == 44 - 7 ); +assert( puke(1, 10) == 10 ); +assert( puke(2, 10, 20) == 20 ); +assert( puke(3, 10, 20, 30) == 30 ); + +try +{ + puke(0); +} +catch (exception exc) +{ + print(exc); + print("\n"); +} diff --git a/src/bin/mu/mu-interp/test/deref.mu b/src/bin/mu/mu-interp/test/deref.mu new file mode 100644 index 000000000..36b263799 --- /dev/null +++ b/src/bin/mu/mu-interp/test/deref.mu @@ -0,0 +1,8 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +float pi = 3.14,q=0; +q += pi; +assert(q == 3.14); diff --git a/src/bin/mu/mu-interp/test/disabled/crowd.mu b/src/bin/mu/mu-interp/test/disabled/crowd.mu new file mode 100644 index 000000000..1f422c38b --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/crowd.mu @@ -0,0 +1,1254 @@ +//****************************************************************************** +// Copyright (c) 2001-2006 Tweak Inc. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +// +// This is a real working piece of code that was used in a movie +// to generate crowds. The processShot() function (at the end +// of the file) was called either by the standalone exporter or by +// the renderer to generate the crowds on the fly. +// +// The behavior of the module is controled by a user parameter file +// which indicates weights for various combonations of sprite attributes. +// The parser for the file is included in the module. Its very brute +// force (not recursive decent, etc). +// + +require io; + +//---------------------------------------------------------------------- +// +// Types +// + +Vec2 := math.vec2f; +Vec3 := math.vec3f; +Point := math.vec3f; +WeightOp := (float;float,float); +WeightFilter := (regex,float); +WeightFilters := WeightFilter[]; +ActionFilter := (regex,float); +ActionFilters := ActionFilter[]; +NamedFilter := (string,regex); +NamedFilters := NamedFilter[]; +StaticFilter := ActionFilter; +StaticFilters := StaticFilter[]; +SpriteFiles := (string,string)[]; +Override := (int,string); // Force id -> sprite +Overrides := Override[]; + +class: ActionType +{ + string name; + int start; + int end; +} + +class: Sprite +{ + string name; + string file; + float weight; + float angle; + bool lit; + ActionType action; + float staticWeight; + int actor; +} + +class: Instance +{ + Sprite sprite; + int frameStart; +} + +class: Parameters +{ + int startFrame; + int endFrame; + string cacheOutput; + string obj; + Point target; + int seed; + bool allowFlip; + bool alwaysFlip; + string outfile; + string particles; + Vec2 cardSize; + int offsetStart; + int offsetEnd; + Vec2 staticOffset; + string suffix; + string geomfile; + string geomobject; + string densityMapFile; + string litMapFile; + float margin; + float skipProbability; + float stagger; + float seperation; + int emitSeed; + Vec3 cardOffset; + string selectMethod; + WeightFilters weightFilters; + ActionFilters actionFilters; + StaticFilters staticFilters; + Overrides overrides; + Sprite[] sprites; +} + +class: KeywordPattern +{ + string word; + regex pattern; +} + +Instances := Instance[]; +ActionTypes := ActionType[]; +SpriteDB := Sprite[]; +KeywordPatterns := KeywordPattern[]; +Backend := (void;Parameters); +SelectionFunc := (Instances; Parameters, int); +SelectionMethod := (string, SelectionFunc); +SelectionMethods := SelectionMethod[]; + +//---------------------------------------------------------------------- +// +// Some helpful operators +// + +\: select_1st (float; float a, float b) { a; } +\: select_2nd (float; float a, float b) { b; } + +\: irandom (int; int a, int b) +{ + math_util.random(float(a), float(b) + 0.9999999999); +} + +\: copy (Sprite; Sprite s) +{ + Sprite n = Sprite(); + n.name = s.name; + n.file = s.file; + n.angle = s.angle; + n.lit = s.lit; + n.weight = s.weight; + n.action = s.action; + n.staticWeight = s.staticWeight; + n; +} + +\: nospaces (string; string text) +{ + let m = regex.smatch("([ \t]*)$", text), + r = text.substr(0, -m[1].size()); + r; +} + +\: parsePoint (Point; string text) +{ + let m = regex.smatch("[ \t]*(-?[0-9.]+)" + + "[ \t]*(-?[0-9.]+)?" + + "[ \t]*(-?[0-9.]+)?", text), + x = float(m[1]), + y = if m[2] == "" then x else float(m[2]), + z = if m[3] == "" then y else float(m[3]); + + Point(x,y,z); +} + +\: padNL (string; string text) +{ + regex.replace("\n", text, " \n"); +} + +\: sortSprites (Sprite[]; Sprite[] array) +{ + \: comp (int; Sprite s1, Sprite s2) + { + let d = s2.weight - s1.weight; + if d < 0 then -1 else (if d > 0 then 1 else 0); + } + + \: qsort (void; Sprite[] a, int lo, int hi) + { + if (lo < hi) + { + let l = lo, + h = hi, + p = a[hi]; + + do + { + while (l < h && comp(a[l],p) <= 0) l++; + while (h > l && comp(a[h],p) >= 0) h--; + + if (l < h) + { + let t = a[l]; + a[l] = a[h]; + a[h] = t; + } + + } while (l < h); + + let t = a[l]; + a[l] = a[hi]; + a[hi] = t; + + qsort(a, lo, l-1); + qsort(a, l+1, hi); + } + } + + qsort(array, 0, array.size() - 1); + array; +} + +module: crowd { + +//---------------------------------------------------------------------- +// +// SpriteType definitions +// +// frame 1 is synch mark +// frame 2 is start of "sit" action +// frame 242 "boo" action +// frame 482 "clap" action (seated) +// frame 842 "sit" (clapping ends) +// frame 962 "stand and cheer" +// frame 1202 "sit" (cheering ends) +// frame 1442 end + +global ActionTypes action_types = +{ + { "sit1", 2, 241 }, + { "boo", 242, 481 }, + { "clap", 482, 841 }, + { "clap2", 621, 841 }, + { "sit2", 842, 961 }, + { "stand", 962, 1201 }, + { "standClap", 1101, 1201 }, + { "sit3", 1202, 1442 }, + { "sit4", 1282, 1442 } +}; + +global SpriteDB sprite_db = nil; + +global NamedFilters named_filters = +{ + ("lit", regex("in_sprite_k.*")), + ("ambient", regex("in_sprite_a.*")), + ("forwardFacing", regex("in_sprite_....f.*")), + ("cameraFacing", regex("in_sprite_....c.*")), + ("winterClothes", regex("in_sprite_.....w.*")), + ("summerClothes", regex("in_sprite_.....s.*")), + ("aClothes", regex("in_sprite_......a.*")), + ("bClothes", regex("in_sprite_......b.*")), + ("teamNeutral", regex("in_sprite_.........n.*")), + ("teamCowboys", regex("in_sprite_.........c.*")), + ("teamEagles", regex("in_sprite_.........e.*")) +}; + +global SpriteFiles sprite_files = +{ + // in_sprite_a000fsa: + ("in_sprite_a000fsa71dA01", ""), + ("in_sprite_a000fsa72dA01", ""), + ("in_sprite_a000fsa74dA01", ""), + ("in_sprite_a000fsa75dA01", ""), + ("in_sprite_a000fsa76dA01", ""), + ("in_sprite_a000fsa77dA01", ""), + ("in_sprite_a000fsa78dA01", ""), + ("in_sprite_a000fsa81dA01", ""), + ("in_sprite_a000fsa82dA01", ""), + ("in_sprite_a000fsa84dA01", ""), + ("in_sprite_a000fsa85dA01", ""), + ("in_sprite_a000fsa86dA01", ""), + ("in_sprite_a000fsa87dA01", ""), + ("in_sprite_a000fsa88dA01", ""), + ("in_sprite_a000fsa91dA01", ""), + ("in_sprite_a000fsa92dA01", ""), + ("in_sprite_a000fsa94dA01", ""), + ("in_sprite_a000fsa95dA01", ""), + ("in_sprite_a000fsa96dA01", ""), + ("in_sprite_a000fsa97dA01", ""), + ("in_sprite_a000fsa98dA01", ""), + ("in_sprite_a000fsa01nB02", ""), + ("in_sprite_a000fsa02nB02", ""), + ("in_sprite_a000fsa04nB02", ""), + ("in_sprite_a000fsa05nB02", ""), + ("in_sprite_a000fsa06nB02", ""), + ("in_sprite_a000fsa07nB02", ""), + ("in_sprite_a000fsa08nB02", ""), + ("in_sprite_a000fsa10nB02", ""), + ("in_sprite_a000fsa12nB02", ""), + ("in_sprite_a000fsa13nB02", ""), + ("in_sprite_a000fsa14nB03", ""), + ("in_sprite_a000fsa16nB02", ""), + ("in_sprite_a000fsa17nB02", ""), + ("in_sprite_a000fsa18nB03", ""), + ("in_sprite_a000fsa19nB03", ""), + ("in_sprite_a000fsa20nB03", ""), + ("in_sprite_a000fsa21eB03", ""), + ("in_sprite_a000fsa24nB03", ""), + ("in_sprite_a000fsa25nB03", ""), + ("in_sprite_a000fsa26nB03", ""), + ("in_sprite_a000fsa27nB10", ""), + ("in_sprite_a000fsa28nB10", ""), + ("in_sprite_a000fsa29eB10", ""), + ("in_sprite_a000fsa31nB10", ""), + ("in_sprite_a000fsa39nB09", ""), + ("in_sprite_a000fsa40nB09", ""), + ("in_sprite_a000fsa41nB09", ""), + ("in_sprite_a000fsa42nB09", ""), + ("in_sprite_a000fsa44nB09", ""), + ("in_sprite_a000fsa45nB10", ""), + ("in_sprite_a000fsa46nB10", ""), + ("in_sprite_a000fsb47nB11", ""), + ("in_sprite_a000fsb48nB11", ""), + ("in_sprite_a000fsb49nB11", ""), + ("in_sprite_a000fsc01nB05", ""), + ("in_sprite_a000fsc02nB04", ""), + ("in_sprite_a000fsc03cB04", ""), + ("in_sprite_a000fsc04nB04", ""), + ("in_sprite_a000fsc05nB04", ""), + ("in_sprite_a000fsc06nB04", ""), + ("in_sprite_a000fsc08nB05", ""), + ("in_sprite_a000fsc09nB05", ""), + ("in_sprite_a000fsc10nB05", ""), + ("in_sprite_a000fsc12nB05", ""), + ("in_sprite_a000fsc13cB05", ""), + ("in_sprite_a000fsc14cB05", ""), + ("in_sprite_a000fsc16nB05", ""), + ("in_sprite_a000fsc17nB05", ""), + ("in_sprite_a000fsc18nB05", ""), + ("in_sprite_a000fsc19nB05", ""), + ("in_sprite_a000fsc20nB05", ""), + ("in_sprite_a000fsc21eB05", ""), + ("in_sprite_a000fsc22nB05", ""), + ("in_sprite_a000fsc24nB05", ""), + ("in_sprite_a000fsc25nB05", ""), + ("in_sprite_a000fsc26nB05", ""), + ("in_sprite_a000fsc50nB11", ""), + + // Additional blue a000 sprites: +// ("in_sprite_a000fsa04nG02", ""), // Deleted 2006-03-20 +// ("in_sprite_a000fsa04nT02", ""), // excited woman is way too saturated to use +// ("in_sprite_a000fsa05nT02", ""), // Deleted 2006-03-20 + ("in_sprite_a000fsa21eT03", ""), + ("in_sprite_a000fsa29eG10", ""), +// ("in_sprite_a000fsa29eT10", ""), // bald guy is way too saturated to use +// ("in_sprite_a000fsa29eX10", ""), // Deleted 2006-03-20 +// ("in_sprite_a000fsa29eY10", ""), // Deleted 2006-03-20 +// ("in_sprite_a000fsa42nT09", ""), // Deleted 2006-03-20 + ("in_sprite_a000fsb48nT11", ""), +// ("in_sprite_a000fsc01nT05", ""), // Deleted 2006-03-20 +// ("in_sprite_a000fsc04nT04", ""), // Deleted 2006-03-20 +// ("in_sprite_a000fsc05nT04", ""), // Deleted 2006-03-20 + ("in_sprite_a000fsc10nT05", ""), +// ("in_sprite_a000fsc21eT05", ""), // Deleted 2006-03-20 + + // in_sprite_k000fs: + ("in_sprite_k000fsa32nB10", ""), + ("in_sprite_k000fsa33nB10", ""), + ("in_sprite_k000fsa34nB10", ""), + ("in_sprite_k000fsa35nB10", ""), + ("in_sprite_k000fsa36nB10", ""), + ("in_sprite_k000fsa47nB07", ""), + ("in_sprite_k000fsd02nB06", ""), + ("in_sprite_k000fsd05nB06", ""), + ("in_sprite_k000fsd06nB06", ""), + ("in_sprite_k000fsd07nB06", ""), + ("in_sprite_k000fsd08nB06", ""), + ("in_sprite_k000fsd09nB06", ""), + ("in_sprite_k000fsd10nB06", ""), + ("in_sprite_k000fsd12nB06", ""), + ("in_sprite_k000fsd14nB06", ""), + ("in_sprite_k000fsd15nB06", ""), + ("in_sprite_k000fsd17nB06", ""), + ("in_sprite_k000fsd18nB06", ""), + ("in_sprite_k000fsd19nB06", ""), + ("in_sprite_k000fsd20nB06", ""), +// ("in_sprite_k000fsd21nB06", ""), // this guy has too dark shirt and pants under a light jacket + ("in_sprite_k000fsd22nB06", ""), + ("in_sprite_k000fsd23nB06", ""), + ("in_sprite_k000fsd24nB07", ""), + ("in_sprite_k000fsd26nB07", ""), + + // Darker-blue sprites added 03/13/2006 + ("in_sprite_a000fsa21eQ03", ""), + ("in_sprite_a000fsb48nQ11", ""), + ("in_sprite_a000fsc05nQ04", ""), + ("in_sprite_a000fsa05nQ02", ""), + ("in_sprite_a000fsc04nQ04", ""), + ("in_sprite_a000fsa42nQ09", ""), + ("in_sprite_a000fsc10nQ05", ""), + ("in_sprite_a000fsc21eQ05", ""), + ("in_sprite_a000fsc01nQ05", ""), + ("in_sprite_a000fsa29eQ10", ""), + + + // in_sprite_a180fs: + ("in_sprite_a180fsa54nA12", ""), + ("in_sprite_a180fsa55nB12", ""), + ("in_sprite_a180fsa56nB12", ""), + ("in_sprite_a180fsa57nB12", ""), + ("in_sprite_a180fsa58nB12", ""), + ("in_sprite_a180fsa60nA12", ""), + ("in_sprite_a180fsa61nA12", ""), + ("in_sprite_a180fsa62nA12", ""), +// ("in_sprite_a180fsa64nB12", "") // <- HAS MISSING FRAMES + + // additional blue a180 sprites: + ("in_sprite_a180fsa54nX12", ""), + ("in_sprite_a180fsa54nY12", ""), + ("in_sprite_a180fsa62nX12", ""), + + // in_sprite_a160fs: + ("in_sprite_a160fsa27nB10", ""), + ("in_sprite_a160fsa28nB10", ""), + ("in_sprite_a160fsa30nB10", ""), + ("in_sprite_a160fsa31nB10", ""), + ("in_sprite_a160fsa38eB10", ""), + ("in_sprite_a160fsa39nB09", ""), + ("in_sprite_a160fsa40nB09", ""), + ("in_sprite_a160fsa42nB09", ""), + ("in_sprite_a160fsa44nB09", ""), + ("in_sprite_a160fsa45nB10", ""), + ("in_sprite_a160fsa46nB10", ""), + ("in_sprite_a160fsb39nB10", ""), + ("in_sprite_a160fsb41nB10", ""), + ("in_sprite_a160fsb46nB11", ""), + ("in_sprite_a160fsb47nB11", ""), + ("in_sprite_a160fsb50nB11", ""), + ("in_sprite_a160fsc38nB11", ""), + ("in_sprite_a160fsc41nB11", ""), + ("in_sprite_a160fsc43nB11", ""), + ("in_sprite_a160fsc45nB11", ""), + ("in_sprite_a160fsc47cB11", ""), + ("in_sprite_a160fsc48nB11", ""), + ("in_sprite_a160fsc49nB11", ""), + ("in_sprite_a160fsc50nB11", ""), + ("in_sprite_a160fsd38nB11", ""), + ("in_sprite_a160fsd41nB11", ""), + + // in_sprite_a030cw -- added 2006-03-17 + ("in_sprite_a030cwa01nA01", ""), + ("in_sprite_a030cwa02nA01", ""), + ("in_sprite_a030cwa03nA01", ""), + ("in_sprite_a030cwa04nA01", ""), + ("in_sprite_a030cwa07nA01", ""), + ("in_sprite_a030cwa08nA01", ""), + ("in_sprite_a030cwa09nA01", ""), + ("in_sprite_a030cwa10nA01", ""), + ("in_sprite_a030cwa11eA01", ""), + ("in_sprite_a030cwa12nA01", ""), + ("in_sprite_a030cwa13nA01", ""), + ("in_sprite_a030cwa14nA01", ""), + ("in_sprite_a030cwa16nA01", ""), + ("in_sprite_a030cwa17nA02", ""), + ("in_sprite_a030cwa18nA01", ""), + ("in_sprite_a030cwa19nA01", ""), + ("in_sprite_a030cwa20nA02", ""), + ("in_sprite_a030cwa21eA02", ""), + ("in_sprite_a030cwa22nA02", ""), + ("in_sprite_a030cwa23nA02", ""), + ("in_sprite_a030cwa24eA02", ""), + ("in_sprite_a030cwa25nA02", ""), + ("in_sprite_a030cwa26nA02", ""), + ("in_sprite_a030cwb31eA09", ""), + ("in_sprite_a030cwb32nA09", ""), + ("in_sprite_a030cwb33nA09", ""), + ("in_sprite_a030cwb34nA09", ""), + ("in_sprite_a030cwb35nA09", ""), + ("in_sprite_a030cwb36nA09", ""), + ("in_sprite_a030cwb37nA09", ""), + ("in_sprite_a030cwb38nA09", ""), + + + // in_sprite_k030cs -- added 2006-03-17 + ("in_sprite_k030csa33nA10", ""), + ("in_sprite_k030csa34nA10", ""), + ("in_sprite_k030csa35nA10", ""), + ("in_sprite_k030csa36nA10", ""), + ("in_sprite_k030csa37nA10", ""), + ("in_sprite_k030csb01nA03", ""), + ("in_sprite_k030csb18nA04", ""), + ("in_sprite_k030csb19nA04", ""), + ("in_sprite_k030csb20nA04", ""), + ("in_sprite_k030csb21nA04", ""), + ("in_sprite_k030csb22nA04", ""), + ("in_sprite_k030csb24nA04", ""), + ("in_sprite_k030csb26cA04", ""), + ("in_sprite_k030csd01nA06", ""), + ("in_sprite_k030csd02nA06", ""), + ("in_sprite_k030csd04nA06", ""), + ("in_sprite_k030csd05nA06", ""), + ("in_sprite_k030csd06nA06", ""), + ("in_sprite_k030csd07nA06", ""), + ("in_sprite_k030csd08nA06", ""), + ("in_sprite_k030csd09nA06", ""), + ("in_sprite_k030csd10nA06", ""), + ("in_sprite_k030csd11nA06", ""), + ("in_sprite_k030csd12nA06", ""), + ("in_sprite_k030csd13nA06", ""), + ("in_sprite_k030csd14nA06", ""), + ("in_sprite_k030csd15nA06", ""), + ("in_sprite_k030csd16nA06", ""), + ("in_sprite_k030csd17nA06", ""), + ("in_sprite_k030csd18nA06", ""), + ("in_sprite_k030csd19nA06", ""), + ("in_sprite_k030csd20nA06", ""), +// ("in_sprite_k030csd21nA06", ""), // this guy has too dark shirt and pantsunder a light jacket + ("in_sprite_k030csd22nA06", ""), + ("in_sprite_k030csd23nA06", ""), + ("in_sprite_k030csd24nA07", ""), + ("in_sprite_k030csd26nA06", ""), + ("in_sprite_k030cse61nA07", ""), + ("in_sprite_k030cse64nA07", ""), + + // Added 2006-03-20 + ("in_sprite_a030csd03nA05", ""), + ("in_sprite_a030csa09nA02", ""), + ("in_sprite_a030csa11nA03", ""), + ("in_sprite_a030csa15nA03", ""), + ("in_sprite_a030csa22nA03", ""), + ("in_sprite_a030csa23nA03", ""), + ("in_sprite_a030csa30nA10", ""), + ("in_sprite_a030csa32nA10", ""), + ("in_sprite_a030csa43nA09", ""), + ("in_sprite_a030csa52nA03", ""), + ("in_sprite_a030csb50nA11", ""), + ("in_sprite_a030csc07nA04", ""), + ("in_sprite_a030csc11eA05", ""), + ("in_sprite_a030csc23eA05", ""), + ("in_sprite_a030csc38nA11", ""), + ("in_sprite_a030csc48nA11", ""), + ("in_sprite_a030csc49nA11", ""), + ("in_sprite_a030fsc15nA05", "") + + +}; + +//---------------------------------------------------------------------- +// +// Build an exhaustive list of "sprites". Sprites, here, are +// defined as ranges with a single sprite "shoot". Since there +// are multiple actions in a single shoot, each action becomes +// a seperate "sprite". +// + +\: buildSpriteDB (SpriteDB; SpriteFiles names, ActionTypes actions) +{ + SpriteDB sprites = SpriteDB(); + + for_each (name; names) + { + let matches = regex.smatch("in_sprite_(.)([0-9]+)(.)(..)([0-9]+)(.)(.*)", + name._0), + lit = matches[1] == 'k', + angle = float(matches[2]), + facing = matches[3], + clothes = matches[4], + actor = matches[5], + team = matches[6], + tape = matches[7]; + + for_each (action; actions) + { + Sprite sprite = Sprite(name._0, name._1, 1.0, + angle, lit, action, 1.0, + int(actor)); + sprites.push_back(sprite); + } + } + + shuffleDB(sprites, 2); + sprites; +} + +//---------------------------------------------------------------------- +// +// The parameter file is a list of name value pairs with the exception +// that certain names are allowed to have an additional argument. In +// most cases the values are probabilities. However, this file also +// handles compiling general parameters to the instancer function. +// +// A lot of the logic in here is for providing useful error messages +// when the file is not syntactically correct. +// + +\: parseParameterFile (Parameters; string filename) +{ + if( ! io.path.exists( filename ) ) + { + print("ERROR: File not found: \"%s\"\n" % (filename)); + throw exception(); + } + let file = io.ifstream(filename), + all = padNL(io.read_all(file)), + lines = string.split(all, "\n\r"), + params = Parameters(1, 100, // start and end frames + "cache", // cacheOutput + "people", // obj + Point(0.0), // target + 0, // seed + true, // allowFlip + false, // alwaysFlip + "", // outfile + "", // particles + Vec2(60,72), // cardSize + 0, // offsetStart + 0, // offsetEnd + Vec2(0,0), // staticOffset + "", // suffix + "", // geomfile + "", // geomobject + "", // densityMapFile + "", // litMapFile + 0.0, 0.0, 0.0, 0.0, // c4 params + 0, // emit seed + Vec3(0,0,0), // cardoffset + "random", // selection method + WeightFilters(), + ActionFilters(), + StaticFilters(), + Overrides() + ), + filters = params.weightFilters, + afilters = params.actionFilters, + sfilters = params.staticFilters, + overrides = params.overrides, + rec = regex("^[ \t]*#.*"), // comment line + reb = regex("^[ \t]*$"), + re = regex("^[ \t]*([a-zA-Z0-9_]+)" + // command + "[ \t]*([^ \t]+)?" + // arg + "[ \t]*=[ \t]*" + + "([^#]*)(#.*)?"); // value + file.close(); + + for (int i=0; i < lines.size(); i++) + { + let line = lines[i]; + + if (rec.match(line) || reb.match(line)) continue; + + \: error (void; string text) + { + print("ERROR: \"%s\", line %d: %s\n" % (filename, i+1, text)); + throw exception(); + } + + try + { + let tokens = re.smatch(line), + name = tokens[1], + arg = tokens[2], + value = tokens[3]; + + //print("line %d tokens = %s\n" % (i, string(tokens))); + + if (name == "angle") + { + if (arg eq nil) + { + error("angle expected an angle in degrees: angle DEG = WEIGHT"); + } + else + { + filters.push_back((regex("in_sprite_.%03d.*" % int(arg)), + float(value))); + } + continue; + } + if (name == "actor") + { + if (arg eq nil) + { + error("actor expected an actor number: actor NUM = WEIGHT"); + } + else + { + filters.push_back((regex("in_sprite_.......%02d.*" % int(arg)), + float(value))); + } + continue; + } + if (name == "action") + { + if (arg eq nil) + { + error("action expected an action name: action NAME = WEIGHT"); + } + else + { + afilters.push_back((regex("^%s$" % arg, regex.Extended), float(value))); + } + continue; + } + if (name == "static") + { + if (arg eq nil) + { + error("static expected an action name: static NAME = WEIGHT"); + } + else + { + sfilters.push_back((regex("^%s$" % arg, regex.Extended), float(value))); + } + continue; + } + if (name == "file") + { + if (arg eq nil) + { + error("file expected a sprite file name or regex: file NAME = WEIGHT"); + } + else + { + filters.push_back((regex(arg), float(value))); + } + continue; + } + if (name == "force") + { + if (arg eq nil) + { + error("force expected a sprite name: force ID = SPRITE"); + } + else + { + overrides.push_back((int(arg), nospaces(value))); + } + continue; + } + if (arg eq nil) + { + if (name == "seed") + { + params.seed = int(value); + continue; + } + if (name == "outfile") + { + params.outfile = nospaces(value); + } + if (name == "particles") + { + params.particles = nospaces(value); + } + if (name == "select") + { + params.selectMethod = nospaces(value); + } + else if (name == "suffix") + { + params.suffix = nospaces(value); + } + else if (name == "geomfile") + { + params.geomfile = nospaces(value); + } + else if (name == "geomobject") + { + params.geomobject = nospaces(value); + } + else if (name == "densityMap") + { + params.densityMapFile = nospaces(value); + } + else if (name == "litMap") + { + params.litMapFile = nospaces(value); + } + else if (name == "seperation") + { + params.seperation = float(value); + } + else if (name == "emitSeed") + { + params.emitSeed = int(value); + } + else if (name == "margin") + { + params.margin = float(value); + } + else if (name == "stagger") + { + params.stagger = float(value); + } + else if (name == "skipProbability") + { + params.skipProbability = float(value); + } + else if (name == "cardSize") + { + let m = regex.smatch("([0-9.]+)[ \t]+([0-9.]+)", value); + params.cardSize = Vec2(float(m[1]), float(m[2])); + } + else if (name == "offset") + { + let m = regex.smatch("([0-9-]+)[ \t]+([0-9-]+)", value); + params.offsetStart = int(m[1]); + params.offsetEnd = int(m[2]); + } + else if (name == "staticOffset") + { + let m = regex.smatch("([0-9-]+)[ \t]+([0-9-]+)", value); + params.staticOffset = Vec2( int(m[1]), int(m[2]) ); + } + else if (name == "flip") + { + let v = nospaces(value); + params.allowFlip = + v == "yes" || + v == "true" || + v == "1"; + } + else if (name == "alwaysFlip") + { + let v = nospaces(value); + params.alwaysFlip = + v == "yes" || + v == "true" || + v == "1"; + } + else if (name == "target") + { + params.target = parsePoint(value); + } + else if (name == "cardOffset") + { + params.cardOffset = parsePoint(value); + } + else if (name == "startFrame") + { + params.startFrame = int(value); + } + else if (name == "endFrame") + { + params.endFrame = int(value); + } + else if (name == "cacheOutput") + { + params.cacheOutput = nospaces(value); + } + else + { + for_each (filter; named_filters) + { + if (filter._0 == name) + { + filters.push_back((filter._1, float(value))); + } + } + } + } + else + { + error("unexpected argument (%s) to %s" % (arg, name)); + } + } + catch (exception exc) + { + error("caught %s" % string(exc)); + throw; + } + catch (...) + { + error("parse error"); + throw; + } + } + + params; +} + +//---------------------------------------------------------------------- +// +// Apply filters in the Parameters object. +// Weight filters (which match names) and action filters which +// match action names. The original DB is munged in the process. +// + +\: applyFilters (SpriteDB; Parameters params, SpriteDB db, WeightOp op) +{ + let ndb = SpriteDB(), + wfilters = params.weightFilters, + afilters = params.actionFilters, + sfilters = params.staticFilters, + accum = 0.0; + + // + // Apply weight filters (across name) + // + + + for_each (s; db) + { + for_each (filter; wfilters) + { + if (filter._0.match(s.name)) + { + s.weight = op(s.weight, filter._1); + } + } + + for_each (filter; afilters) + { + if (filter._0.match(s.action.name)) + { + s.weight = op(s.weight, filter._1); + } + } + for_each (filter; sfilters) + { + if (filter._0.match(s.action.name)) + { + s.staticWeight = op(s.staticWeight, filter._1); + } + } + + if (s.weight > 0.0) + { + accum += s.weight; + ndb.push_back(s); + } + } + + if( ndb.size() == 0 ) + { + print("\nNo sprites matched filter criteria!\n"); + throw exception(); + } + + for_each (s; ndb) s.weight /= accum; + + //sortSprites(ndb); + ndb; +} + +//---------------------------------------------------------------------- +// +// Shuffle sprite instances +// + +\: shuffle (void; Instances instances) +{ + for (int i=0, n=instances.size(); i < n; i++) + { + let r = irandom(i, n-1), + t = instances[r]; + + instances[r] = instances[i]; + instances[i] = t; + } +} + +\: neighborShuffle (void; Instances instances) +{ + for (int i=0, n=instances.size()-1; i < n; i++) + { + let a = instances[i], + b = instances[i+1]; + + if (a.sprite.name == b.sprite.name) + { + let t = instances[-1]; + instances[-1] = instances[i]; + instances[i] = t; + } + } +} + +\: shuffleDB (void; SpriteDB db, int num) +{ + repeat (num) + { + for (int i=0, n=db.size(); i < n; i++) + { + let r = irandom(i, n-1), + t = db[r]; + + db[r] = db[i]; + db[i] = t; + } + } +} + +//---------------------------------------------------------------------- +// +// Choose random sprite +// + + +\: chooseSprite (Sprite; SpriteDB db) +{ + // Hack to get around unresolved func bug + (float;float) F = math_util.random; + + let r = F(1.0), + a = 0.0; + + Sprite rval = nil; + + for_each (s; db) + { + let w = s.weight; + + if (a + w > r) + { + rval = s; + break; + } + + a += w; + } + + rval; +} + +//---------------------------------------------------------------------- +// +// Generate a sprite instance +// + +\: chooseOneRandom (Instance; Parameters params) +{ + let s = chooseSprite(params.sprites), + f = irandom(params.offsetStart, params.offsetEnd) + s.action.start; + + Instance(s, math.max(f,1) ); +} + +// for backwards compat +generateInstance := chooseOneRandom; + +//---------------------------------------------------------------------- +// +// Generate N instances using the linear shuffle method +// + +\: chooseLinearShuffle (Instances; Parameters params, int n) +{ + chooseLinearShuffleWithSeed(params, n, nil); +} + +\: chooseLinearShuffleWithSeed (Instances; Parameters params, int n, (void;int) seedFunc) +{ + Instances instances; + math_util.seed(params.seed + params.emitSeed); + + class: SpriteIterator + { + SpriteDB db; + Sprite sprite; + float end; + float current; + } + + \: inc (SpriteIterator; SpriteIterator i) + { + i.current = (i.current+1) % i.db.size(); + i.sprite = i.db[i.current]; + i.end += i.sprite.weight; + i; + } + + // + // Choose instances by linearly walking through + // sprite db. + // + + print("INFO: selecting sprites by linear walk\n"); + + let si = SpriteIterator(params.sprites, params.sprites.front(), 0, 0), + start = params.offsetStart, + end = params.offsetEnd; + + for (int i=0; i < n; i++) + { + float d = 1.0 / n * float(i); + while (d > si.end) inc(si); + + if (!(seedFunc eq nil)) seedFunc(i); + + let s = si.sprite, + f = math.max(irandom(start, end) + s.action.start, 1); + + instances.push_back(Instance(s, f)); + } + + shuffle(instances); + neighborShuffle(instances); + instances; +} + +//---------------------------------------------------------------------- +// +// Choose using the random selection method +// + + +\: chooseRandomSelectionWithSeed (Instances; + Parameters params, + int n, + (void;int) seedFunc) +{ + Instances instances; + math_util.seed(params.seed + params.emitSeed); + + for (int i=0; i < n; i++) + { + seedFunc(i); + instances.push_back(chooseOneRandom(params)); + } + + shuffle(instances); + neighborShuffle(instances); + instances; +} + +\: chooseRandomSelection (Instances; Parameters params, int n) +{ + // BUG: why can't I overload chooseRandomSelection instead of + // using a unique name (chooseRandomSelectionWithSeed). + chooseRandomSelectionWithSeed(params, n, \: (void;int i) {;}); +} + +//---------------------------------------------------------------------- +// +// Selection method database +// + +global SelectionMethods methods = { ("random", chooseRandomSelection), + ("linear", chooseLinearShuffle) }; + +\: smethod (SelectionFunc; string name) +{ + for_each (m; methods) + { + if (m._0 == name) return m._1; + } + + print("WARNING: no selection methods called \"%s\", using random\n" % name); + methods.front()._1; +} + +//---------------------------------------------------------------------- +// +// +// + +\: buildParameters (Parameters; SpriteDB sprites, string filename) +{ + let params = parseParameterFile(filename), + finalSprites = applyFilters(params, sprites, (*)); + + params.sprites = finalSprites; + params; +} + +//---------------------------------------------------------------------- +// +// Dummy backend for processShot() +// + +\: debugBackend (void; Parameters params) +{ + print("\nPARAMETERS\n%s\n" % string(params)); + + print("\nweight filters\n"); + for_each (f; params.weightFilters) + { + print("%s\n" % string(f)); + } + + print("\naction filters\n"); + for_each (f; params.actionFilters) + { + print("%s\n" % string(f)); + } + + print("\nfinal sprites\n"); + for_each (s; params.sprites) + { + print("%s\n" % string(s)); + } +} + +\: nullBackend (void; Parameters params) { ; } + + +// +// call this curried with n = how many you want +// + +\: chooseNSprites (Instances; Parameters params, int n, (void;int) F = nil) +{ + print("INFO: method = %s\n" % params.selectMethod); + + if (params.selectMethod == "linear") + { + if (F eq nil) + { + return chooseLinearShuffle(params, n); + } + else + { + return chooseLinearShuffleWithSeed(params, n, nil); + } + } + else + { + if (F eq nil) + { + return chooseRandomSelection(params, n); + } + else + { + return chooseRandomSelectionWithSeed(params, n, F); + } + } +} + +\: chooseNBackend (void; Parameters params, int n) +{ + for_each (s; chooseNSprites(params, n)) + { + print("%s\n" % string(s)); + } +} + +\: silentChooseNBackend (void; Parameters params, int n) +{ + chooseNSprites(params, n); +} + +//---------------------------------------------------------------------- +// +// Main entry point +// + +\: processShot (void; string paramfile, Backend backend) +{ + math_util.seed(132); + + let sprites = buildSpriteDB(sprite_files, action_types), + params = buildParameters(sprites, paramfile); + + backend(params); +} + +} // module: crowd + +// +// Call it with the debugging backend that just chooses N +// sprites and spits them out. +// + +crowd.processShot("testdata/crowd_L13.parameters", + crowd.silentChooseNBackend(,10000)); diff --git a/src/bin/mu/mu-interp/test/disabled/doc.mu b/src/bin/mu/mu-interp/test/disabled/doc.mu new file mode 100644 index 000000000..d1ce878a6 --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/doc.mu @@ -0,0 +1,34 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +require autodoc; +require math; +require runtime; +require math_util; + +function: potato (void;) +{ + int x = 1.0; + print(x + "\n"); +} + +float[3,3,3] a; +vec := vector float[3]; + +try +{ + string sym = "float[3,3,3]"; + string doc = autodoc.document_symbol(sym, 0, autodoc.Text); + + print("Documentation for symbol: " + sym + "\n"); + print("\n"); + print(doc + "\n"); +} +catch (exception exc) +{ + print("caught: "); + print(exc); + print("\n"); +} diff --git a/src/bin/mu/mu-interp/test/disabled/docstring.mu b/src/bin/mu/mu-interp/test/disabled/docstring.mu new file mode 100644 index 000000000..15ef7616f --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/docstring.mu @@ -0,0 +1,91 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + + +documentation: bar +"Here's some global variable +Does it work:?"; + +global float bar = 1.0; + +//---------------------------------------------------------------------- +documentation: +"Function documentation could live right here. It could be +long enough that you get a feeling for how the comment would +appear in real source code. Or maybe not: + + May be there will be intendation? + +Maybe there will be none. I don't know"; + +documentation: a +"A is the name of some parameter taken by foo"; + +\: foo (float; float a) +{ + return math.pow(a * a + a / a, 1.0 / a); +} + +documentation: +"Baz is a function. +That's documented here."; + +\: baz (float; float b) +{ + b + b; +} + +documentation: add +"Add is (+)"; + +documentation: a +"A is the first number to add."; + +documentation: b +"B is the second number to Add"; + +\: add (int; int a, int b ) { a + b; } +require autodoc; +print("BAR: \n" + autodoc.document_symbol("bar")); +print("FOO: \n" + autodoc.document_symbol("foo")); +print("FOO.a: \n" + autodoc.document_symbol("foo.a")); +print("BAZ: \n" + autodoc.document_symbol("baz")); +print("ADD: \n" + autodoc.document_symbol("add")); +print("ADD.a: \n" + autodoc.document_symbol("add.a")); +print("ADD.b: \n" + autodoc.document_symbol("add.b")); + + +\: f1 (int; int a) { return a; } +\: f1 (int; int a, int b) { return a + b; } +\: f2 (int; int a) { return a; } + +class: A +{ + class: B + { + int b; + } + + int a; +} + +// a documentation module +documentation: { + +A.B "some A.B docs" +f1.a "blah blah blah" +f1 (int;int) "f1 docs" +f1 (int;int,int) "f1#2 docs" +f2 "f2 docs" + + +} + + +print("-------f1: \n" + autodoc.document_symbol("f1")); +print("-------f1.a: \n" + autodoc.document_symbol("f1.a")); +print("-------f2: \n" + autodoc.document_symbol("f2")); +print("-------B: \n" + autodoc.document_symbol("A.B")); diff --git a/src/bin/mu/mu-interp/test/disabled/dynamic_cast.mu b/src/bin/mu/mu-interp/test/disabled/dynamic_cast.mu new file mode 100644 index 000000000..68d374bb6 --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/dynamic_cast.mu @@ -0,0 +1,18 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +use test; + +base_class base = a_class("BASE"); +a_class a = base; + +try +{ + b_class b = base; +} +catch (exception exc) +{ + print("CAUGHT: " + string(exc) + "\n"); +} diff --git a/src/bin/mu/mu-interp/test/disabled/encode.mu b/src/bin/mu/mu-interp/test/disabled/encode.mu new file mode 100644 index 000000000..2fd4a01a1 --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/encode.mu @@ -0,0 +1,27 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +use encoding; + + +\: test (void;) +{ + let s = "hello", + u = string_to_utf8(s), + b = to_base64(u), + e = utf8_to_string(b), + r = from_base64(b); + + assert(e == "aGVsbG8="); + print("s = %s\n" % s); + print("u = %s\n" % u); + print("b = %s\n" % b); + print("e = %s\n" % e); + print("r = %s\n" % r); + assert(utf8_to_string(r) == s); +} + +test(); + diff --git a/src/bin/mu/mu-interp/test/disabled/interface.mu b/src/bin/mu/mu-interp/test/disabled/interface.mu new file mode 100644 index 000000000..58be26b43 --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/interface.mu @@ -0,0 +1,22 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +use test; + +// +// Test interfaces +// + +int[] x = {1, 2, 3}; +sequence c = x; +c.clear(); +c.push_back(3); +c.push_back(1); +c.push_back(2); +c.pop_back(); +c.push_back(10); +assert(x == int[] {3, 1, 10}); +c; diff --git a/src/bin/mu/mu-interp/test/disabled/io_binary.mu b/src/bin/mu/mu-interp/test/disabled/io_binary.mu new file mode 100644 index 000000000..64374fcca --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/io_binary.mu @@ -0,0 +1,22 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +use io; + +let file = ofstream("/tmp/test.binary"); +file.write(byte(1)); +file.write(byte(2)); +file.write(byte(3)); + +string s = "hello world"; +byte[] bytes; + +for (int i=0; i < s.size(); i++) +{ + bytes.push_back(s[i]); +} + +file.write(bytes); +file.close(); diff --git a/src/bin/mu/mu-interp/test/disabled/io_process.mu b/src/bin/mu/mu-interp/test/disabled/io_process.mu new file mode 100644 index 000000000..22e52da5f --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/io_process.mu @@ -0,0 +1,27 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +\: doit (void;) +{ + use io; + bool windows = runtime.build_os() == "WINDOWS"; + let cmdstring = if windows then "C:/cygwin/bin/ls" else "/bin/ls"; + + process cmd = process(cmdstring, ["-al", "test"], int64.max); + istream lsout = cmd.out(); + cmd.close_in(); + + while (true) + { + let line = read_line(lsout, '\n'); + if (line == "") break; + print("%s\n" % line); + } + + cmd.close(); +} + +doit(); diff --git a/src/bin/mu/mu-interp/test/disabled/io_test.mu b/src/bin/mu/mu-interp/test/disabled/io_test.mu new file mode 100644 index 000000000..e37525784 --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/io_test.mu @@ -0,0 +1,144 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// Test input/output streams. +// +// Just for the fun, try and duplicate C++'s "stream operator" +// syntax on top of Mu's io module. +// + +use io; + +string testfile = "test.file"; + +// +// ManipFunc is a function that takes an ostream and returns an ostream +// + +ManipFunc := (ostream; ostream); + +// +// These are the output and input operators. Since ostream uses +// print() these operators convert the stream-like syntax into +// regular function calls. +// + +operator: << (ostream; ostream o, string s) { print(o, s); } +operator: << (ostream; ostream o, int i) { print(o, i); } +operator: << (ostream; ostream o, bool i) { print(o, i); } +operator: << (ostream; ostream o, float i) { print(o, i); } +operator: << (ostream; ostream o, byte b) { print(o, b); } + +// +// This output operator mimics C++ stream manipulators. The operator +// takes a stream and a function that takes a stream. It calls it +// with the passed in stream returning a stream. This is convenient +// since the io module includes functions that already have this +// signature. +// + +operator: << (ostream; ostream o, ManipFunc x) { x(o); } + +// +// Now try and use all of the above. +// "endl" and "flush" are actually stream functions which have the +// same signature as ManipFunc, so you can just use them as stream +// operator arguments without modification. +// + +function: write_it (ostream; ostream o) +{ + o << "hello world" + << ", int = " << 123 + << ", float = " << 456.789 // currently mu defaults to float if no suffix given + << ", bool = " << true + << ", byte = " << byte(64) + << ", string = " << "foo" + << endl + << flush; +} + +function: read_it (istream; istream i) +{ + string in_string; + int in_int; + float in_float; + bool in_bool; + byte in_byte; + + read_string(i, 4); in_int = read_int(i); + read_string(i, 3); in_float = read_float(i); + read_string(i, 3); in_bool = read_bool(i); + read_string(i, 3); in_byte = read_byte(i); + read_string(i, 3); in_string = read_string(i); + read_string(i); + + //print("in_int = %s\n" % in_int); + //print("in_float = %s\n" % in_float); + //print("in_bool = %s\n" % in_bool); + //print("in_byte = %s\n" % in_byte); + //print("in_string = %s\n" % in_string); + + assert(in_int == 123); + assert(in_float == 456.789); + assert(in_bool == true); + assert(in_byte == 64); + assert(in_string == "foo"); + + assert(i.eof()); + i; +} + +// +// Write the file +// + +ofstream ofile = ofstream(testfile); +write_it(ofile); +ofile.close(); + +// +// Read it back +// + +ifstream ifile = ifstream(testfile); +string stuff; + +if (ifile.good() && !ifile.bad() && !ifile.fail() && !ifile.eof()) +{ + stuff = ifile.gets("\n"); +} + +let o = "hello world, int = 123, float = 456.789, bool = 1, byte = 64, string = foo"; +if (stuff != o) +{ + print("STUFF = %s\n" % stuff); + print("O = %s\n" % o); +} +assert(stuff == o); +ifile.close(); + +// +// Now use an osstream on write_it() +// + +osstream ss = osstream(); +write_it(ss); +stuff = string(ss); +assert(stuff == (o + "\n")); + +// +// Finally, let's try and scan it. +// + +isstream is = isstream(stuff); +read_it(is); + +ifile = ifstream(testfile); +read_it(ifile); +ifile.close(); + +write_it(out()); diff --git a/src/bin/mu/mu-interp/test/disabled/linear_basic.mu b/src/bin/mu/mu-interp/test/disabled/linear_basic.mu new file mode 100644 index 000000000..f4509b2ec --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/linear_basic.mu @@ -0,0 +1,80 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +require math_linear; +use io; + +M44 := float[4,4]; +Vec4 := vector float[4]; +Vec3 := vector float[3]; + +\: format (string; M44 M) +{ + ostream out = osstream(); + + for_index (i,j; M) + { + print(out, " %g" % M[i,j]); + if (j == 3) print(out, "\n"); + } + + string(out); +} + +\: format (string; float[5,5] M) +{ + ostream out = osstream(); + + for_index (i,j; M) + { + print(out, " %g" % M[i,j]); + if (j == 4) print(out, "\n"); + } + + string(out); +} + +\: test () +{ + let A = M44(2, 0, 0, 0, + 0, 2, 0 ,0, + 0, 0, 2, 0, + 0, 0, 0, 1); + + let B = M44(2, 0, 0, 0, + 0, 2, 0 ,0, + 0, 0, 2, 0, + 0, 0, 0, 1); + + let M = M44(2, 0, 0, 0, + 0, 2, 0 ,0, + 0, 0, 2, 0, + 0, 0, 0, 1); + + let Q = float[5,5] {2, 0, 0, 0, 0, + 0, 2, 0 ,0, 0, + 0, 0, 2, 0, 0, + 0, 0, 0, 2, 0, + 0, 0, 0, 0, 1}; + + float[100,100] BIG; + for_index (i, j; BIG) if (i == j) BIG[i,j] = 2.0; + + let v4 = Vec4(1,2,3,1); + let v3 = Vec3(1,2,3); + + print("M = \n%s" % format(M)); + print("inverse(M) = \n%s" % format(inverse(M))); + print("Q = \n%s" % format(Q)); + print("inverse(Q) = \n%s" % format(inverse(Q))); + inverse(BIG); + print("M * v4 = %s\n" % (M * v4)); + print("M * v3 = %s\n" % (M * v3)); + print("A * B = \n%s" % format(A * B)); + +} + + +test(); diff --git a/src/bin/mu/mu-interp/test/disabled/mutual_recursion.mu b/src/bin/mu/mu-interp/test/disabled/mutual_recursion.mu new file mode 100644 index 000000000..20fcc804f --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/mutual_recursion.mu @@ -0,0 +1,62 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// This test is mainly useful for determining if unresolved calls are +// handled properly. In this case, mutual recursion needs to be +// resolved. This example prints out a random mu expression (idea +// from something I saw on the web when looking up mutual recursion). +// + +use io; +use math_util; +use math; + +global string[] ops = {" + ", " - ", " * ", " / ", " % "}; +global string[] funcs = { "math.sin", "math.cos", "math.tan", "math.sqrt", "math.pow", + "math.asin", "math.acos", "math.atan", "math.cbrt" }; + +\: infixExpression (void; ostream out, int depth) +{ + print(out, "("); + randomExpression(out, depth); + print(out, ops[random(ops.size())]); + randomExpression(out, depth); + print(out, ")"); +} + +\: callExpression (void; ostream out, int depth) +{ + print(out, funcs[random(funcs.size())] + "("); + randomExpression(out, depth); + print(out, ")"); +} + +\: randomExpression (void; ostream out, int depth) +{ + let r = random(10); + + if (r < depth) + { + if (r % 4 == 0) callExpression(out, depth-1); + else infixExpression(out, depth-1); + } + else + { + print(out, string(random(1.0))); + } +} + +\: doit (void; int level) +{ + randomExpression(out(), level); + print(out(), "\n"); +} + +for (int i=0; i < 25; i++) +{ + seed(2239 + i); + doit(7); +} diff --git a/src/bin/mu/mu-interp/test/disabled/serialize_array.mu b/src/bin/mu/mu-interp/test/disabled/serialize_array.mu new file mode 100644 index 000000000..d98696610 --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/serialize_array.mu @@ -0,0 +1,34 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +use io; +BigMess := int[2,2][][]; +BigMess foo; + +for (int j=0; j < 10; j++) +{ + foo.push_back(int[2,2][]()); + + for (int i=0; i < j * 10; i++) + { + foo.back().push_back(int[2,2] {i, j, j, i}); + } +} + +string dump = string(foo); + +{ + ofstream f = ofstream("/tmp/test.muc"); + serialize(f, foo); + f.close(); +} + +{ + ifstream f = ifstream("/tmp/test.muc"); + BigMess bar = deserialize(f); + assert(dump == string(bar)); + f.close(); +} diff --git a/src/bin/mu/mu-interp/test/disabled/serialize_funcobj.mu b/src/bin/mu/mu-interp/test/disabled/serialize_funcobj.mu new file mode 100644 index 000000000..775a5bbd1 --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/serialize_funcobj.mu @@ -0,0 +1,18 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +use io; + +(float;float) F = \: (float;float a) { a + 68; }; + +let ofile = ofstream("/tmp/test.muc"); +let n = serialize(ofile, F); +ofile.close(); +n; + +let ifile = ifstream("/tmp/test.muc"); +(float;float) G = deserialize(ifile); +ifile.close(); +G(1.0); diff --git a/src/bin/mu/mu-interp/test/disabled/xmlrpc.mu b/src/bin/mu/mu-interp/test/disabled/xmlrpc.mu new file mode 100644 index 000000000..6fb8463bf --- /dev/null +++ b/src/bin/mu/mu-interp/test/disabled/xmlrpc.mu @@ -0,0 +1,124 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +use io; +use encoding; + +union: Value +{ + Nil + | Int int + | String string + | Bool bool + | Double float + | DateTime (int,int,int,int,int,int) + | Binary byte[] + | Struct [(string, Value)] + | EmptyArray + | Array [Value] +} + +function: outputValue (void; ostream o, Value v) +{ + case (v) + { + Nil -> { print(o, ""); } + Int i -> { print(o, "%d" % i); } + String s -> { print(o, "%s" % s); } + Bool b -> { print(o, "%d" % (if b then 1 else 0)); } + Double f -> { print(o, "%g" % f); } + EmptyArray -> { print(o, ""); } + DateTime d -> { print("%04%02%02T%02:%02:%02" % d); } + + Struct s -> + { + print(o, ""); + + for_each (p; s) + { + print(o, "%s" % p._0); + outputValue(o, p._1); + print(o, ""); + } + + print(o, ""); + } + + Array a -> + { + print(o, ""); + for_each (e; a) outputValue(o, e); + print(o, ""); + } + + Binary b -> + { + print(o, ""); + print(o, utf8_to_string(to_base64(b))); + print(o, ""); + } + } +} + +function: outputParamList (void; ostream o, [Value] vlist) +{ + print(o, ""); + + for_each (v; vlist) + { + print(o, ""); + outputValue(o, v); + print(o, ""); + } + + print(o, ""); +} + +function: outputMethod (void; ostream o, string methodName, [Value] paramList) +{ + print(o, "%s" % methodName); + outputParamList(o, paramList); + print(o, ""); +} + +function: xmlrpc (void; + string name, + [Value] params, + (void; Value) rvalFunc) +{ +// httpPost("https://rvdemo.shotgridstudio.com/api3_preview/", +// [("Content-Type", "text/xml")], +// "", +// "xmlrpc-return", +// "xmlrpc-authenticate", +// "xmlrpc-error"); + ; +} + + +Member := (string, Value); +Param := Value; + +use Value; + +let p1 = Struct( [ ("script_name", String("rv") ), + ("script_key", String("4b1676497a208c845b12f5c6734daf9d6e7c6274")) ] ); + +let p2 = Struct( [ ("paging", + Struct( [("current_path", Int(1)), + ("entities_per_page", Int(500))] )), + + ("filters", + Struct( [("conditions", EmptyArray), + ("logical_operator", String("and"))] )) + + ] ); + +osstream str; +outputMethod(str, "read", [p1, p2]); +print("str = "); +print(string(str)); +print("\n"); + diff --git a/src/bin/mu/mu-interp/test/exception.mu b/src/bin/mu/mu-interp/test/exception.mu new file mode 100644 index 000000000..e6b32165e --- /dev/null +++ b/src/bin/mu/mu-interp/test/exception.mu @@ -0,0 +1,64 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +function: print_trace (void; exception exc) +{ + string[] trace = exc.backtrace(); + + for (int i=0; i < trace.size(); i++) + { + if (i < 100) print(" "); + if (i < 10) print(" "); + print(i + ": " + trace[i] + "\n"); + } +} + +try +{ + try + { + try + { + try + { + throw exception("the thing that was thrown!"); + } + catch (exception e) + { + print("caught this: "); + print(e); + print("\n"); + print_trace(e); + print("about to rethrow\n"); + throw; // rethrow + } + } + catch (exception e) + { + print("outside caught this: "); + print(e); + print("\n"); + print_trace(e); + throw; + } + catch (...) + { + print("caught something I shouldn't have\n"); + } + } + catch (...) + { + print("successfully caught whatever\n"); + throw; + } +} +catch (exception e) +{ + print("after rethrow of unknown caught: "); + print(e); + print("\n"); + print_trace(e); +} diff --git a/src/bin/mu/mu-interp/test/experimental.mu b/src/bin/mu/mu-interp/test/experimental.mu new file mode 100644 index 000000000..95b2fcfb7 --- /dev/null +++ b/src/bin/mu/mu-interp/test/experimental.mu @@ -0,0 +1,13 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + + +module: experimental +{ + class: experimental { int a; } +} + +class: slice { int start; int end; int step; } diff --git a/src/bin/mu/mu-interp/test/factorial.mu b/src/bin/mu/mu-interp/test/factorial.mu new file mode 100644 index 000000000..0db99a516 --- /dev/null +++ b/src/bin/mu/mu-interp/test/factorial.mu @@ -0,0 +1,33 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// An attempt at getting tail recursion working +// + +\: factorial_recursive (int; int x) +{ + if x == 1 then 1 else x * factorial_recursive(x-1); +} + +\: factorial_tail_recursive (int; int n) +{ + \: iterate (int; int n, int acc) + { + if n <= 1 then acc else iterate(n-1, acc*n); + } + + return iterate(n, 1); +} + +int n = 15; +print(factorial_tail_recursive(n) + "\n"); +assert(factorial_recursive(n) == factorial_tail_recursive(n)); + +(int; int) factorial_type_function = nil; +factorial_type_function = factorial_recursive; +factorial_type_function = factorial_tail_recursive; +(int; int) other_func = factorial_type_function; diff --git a/src/bin/mu/mu-interp/test/fixed_array.mu b/src/bin/mu/mu-interp/test/fixed_array.mu new file mode 100644 index 000000000..e48a9424b --- /dev/null +++ b/src/bin/mu/mu-interp/test/fixed_array.mu @@ -0,0 +1,57 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +int[4] array; +array[0] = 1; +array[1] = 2; +array[2] = 3; +array[3] = 4; + +int[4] copiedArray = int[4](array); +copiedArray[0] = 10; +assert(copiedArray[0] != array[0]); +assert(copiedArray[3] == 4); +assert(copiedArray[2] == 3); +assert(copiedArray[1] == 2); + +for (int i=0; i < 4; i++) assert(array[i] == i+1); + +float[4][] farray; +farray.push_back( float[4]() ); +farray.back()[0] = 1; +farray.back()[1] = 2; +farray.back()[2] = 3; +farray.back()[3] = 4; + +int[4][][10][100,50,1][][][][59,1] jesus; + +float[4,4] M; +M[0,0] = 10.0; +assert(M[0,0] == 10.0); + +float[4,4][1000] matrices; + +for (int i=0; i < 1000; i++) +{ + matrices[i] = float[4,4](); + + for (int j=0; j<4; j++) + { + for (int k=0; k<4; k++) + { + if (j == k) + { + matrices[i][j,k] = 1; + } + else + { + matrices[i][j,k] = 0; + } + } + } +} + + diff --git a/src/bin/mu/mu-interp/test/fizzbuzz.mu b/src/bin/mu/mu-interp/test/fizzbuzz.mu new file mode 100644 index 000000000..02ebf07c6 --- /dev/null +++ b/src/bin/mu/mu-interp/test/fizzbuzz.mu @@ -0,0 +1,21 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +\: fizzbuzz (void; int start, int end) +{ + for (int i=start; i <=end; i++) + { + let m3 = i % 3 == 0, + m5 = i % 5 == 0; + + if (m3) print("Fizz"); + if (m5) print("Buzz"); + if (!m3 && !m5) print("%d" % i); + print("\n"); + } +} + +fizzbuzz(1,100); diff --git a/src/bin/mu/mu-interp/test/foldl.mu b/src/bin/mu/mu-interp/test/foldl.mu new file mode 100644 index 000000000..866c539bb --- /dev/null +++ b/src/bin/mu/mu-interp/test/foldl.mu @@ -0,0 +1,48 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +A := int; +B := int; +Op := (A;A,B); + +\: foldl (A; Op f, A seed, [B] l) +{ + if (l eq nil) + { + return seed; + } + else + { + let h:t = l; + return foldl(f, f(h, seed), t); + } +} + +\: filter ([A]; (bool;A) p, [A] l) +{ + if (l eq nil) + { + return nil; + } + else + { + let h:t = l; + + if (p(h)) + { + return h : filter(p, t); + } + else + { + return filter(p,t); + } + } +} + +\: even (bool; int a) { return (a & 1) == 0; } + +foldl((+), 0, [1,2,3,4]); + +filter(even, [1,2,3,4]); diff --git a/src/bin/mu/mu-interp/test/for.mu b/src/bin/mu/mu-interp/test/for.mu new file mode 100644 index 000000000..edffa9580 --- /dev/null +++ b/src/bin/mu/mu-interp/test/for.mu @@ -0,0 +1,32 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +int i = 69; + +for (int i=0; i<10; i+=1) +{ + assert(i >= 0 && i < 10); + print(i); +} + +assert(i == 69); + +while (i>60) +{ + print(i); + assert(i > 60); + i = i - 1; +} + +assert(i == 60); + +do +{ + i += 1; + print(i); + assert(i <= 69); +} while (i < 69); + +assert(i == 69); diff --git a/src/bin/mu/mu-interp/test/for_each.mu b/src/bin/mu/mu-interp/test/for_each.mu new file mode 100644 index 000000000..b99a166d4 --- /dev/null +++ b/src/bin/mu/mu-interp/test/for_each.mu @@ -0,0 +1,63 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +int[] iarray = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; + +string[] sarray = {"one", "two", "three", + "four", "five", "six", + "seven", "eight", "nine", + "ten"}; + +float[4,4] M = {1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1}; + + +float[][] farray = { {1,2,3}, {4,5,6}, {7,8,9}, {10,10,10} }; + +for_each (i; iarray) print(i + string(" ")); +print("\n"); + +int count = 1; +for_each (i; iarray) assert(i == count++); +assert(count == iarray.size() + 1); + +for_each (i; sarray) print(i + string(" ")); +print("\n"); + +for_each (i; M) print(i + string(" ")); +print("\n"); + + +for_each (i; farray) +{ + print(i.back() + string(" ")); +} + +for_each (i; iarray) +{ + if (i == 5) break; + assert(i < 5); +} + +bool foundSix = false; + +print("\n"); +print("iarray = %s\n" % iarray); +for_each (i; iarray) +{ + if (i == 5) { print(" i=5 "); continue; } + print(" %d" % i); + //assert(i > 5 || i < 5); + if (i == 6) foundSix = true; +} + +assert(foundSix); + +print("\n"); + + diff --git a/src/bin/mu/mu-interp/test/for_index.mu b/src/bin/mu/mu-interp/test/for_index.mu new file mode 100644 index 000000000..1fe28ecf9 --- /dev/null +++ b/src/bin/mu/mu-interp/test/for_index.mu @@ -0,0 +1,41 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +string[] array = {"one", "two", "three", "four", "five"}; +int[] iarray = {1,2,3,4,5}; + +for_index (i; array) +{ + print("%s = %d\n" % (array[i], iarray[i])); +} + +// +// This function should be marked pure +// but its not. +// + +\: transpose(float[4,4]; float[4,4] M) +{ + float[4,4] N; + for_index (i,j; M) N[j,i] = M[i,j]; + N; +} + + +let M = transpose( float[4,4] {1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 1, 2, 3, 1} ); + +assert(M[1,3] == 2 && M[2,3] == 3); + +int[2,2,2] X = {1,2,3,4,5,6,7,8}; + +for_index (i,j,k; X) +{ + print("i,j,k => %d,%d,%d = %d\n" % (i,j,k,X[i,j,k])); + assert(X[i,j,k] == i * (2 * 2) + j * 2 + k + 1); +} diff --git a/src/bin/mu/mu-interp/test/format.mu b/src/bin/mu/mu-interp/test/format.mu new file mode 100644 index 000000000..ccfc4ed95 --- /dev/null +++ b/src/bin/mu/mu-interp/test/format.mu @@ -0,0 +1,21 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +assert("image.%04d.jpg" % 4 == "image.0004.jpg"); +assert("foo%d" % 1 == "foo1"); +assert("%dfoo" % 1 == "1foo"); +assert("math.pi is %g and math.e is %g" % (math.pi, math.e) == + "math.pi is 3.14159 and math.e is 2.71828"); + + +string x = ""; + +for (int i = 0; i < 4097; ++i) +{ + assert(x.size() == i); + x = "%s." % x; +} + diff --git a/src/bin/mu/mu-interp/test/free.mu b/src/bin/mu/mu-interp/test/free.mu new file mode 100644 index 000000000..540d6619d --- /dev/null +++ b/src/bin/mu/mu-interp/test/free.mu @@ -0,0 +1,111 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// This is a test of closure generation +// +// Note: these function definitions are purposefully omitting the +// return type in order to work the inference mechanism. +// + +int n = 10; + +// +// Free variable "n" will be bound to top scope "n" if called from the top +// scope. +// +// addn signature is: (int;int) + +\: addn (int x) { x + n; } + + + +// +// Free variable n in internal lambda expression will be bound to the +// parameter passed into adder(). +// +// adder signature is: ((int;int);int) + +\: adder (int n) { \: (int x) { x + n; }; } + +// +// The internal function is bound and is returned. The return expression +// is a lambda expression: so the free variable is captured at that time. +// +// adder_literal signature is: ((int;int);int) + +\: adder_literal (int n) { \: F (int x) { x + n; }; F; } + +// +// I'm not clear as to whether this breaks the definition of closure +// in scheme. But it makes sense in the context of Mu's +// implementation of a "closure" (free variables are hoisted to named +// parameters which automatically match symbols in the context of a +// call or function object creation). +// +// adder_literal2 signature is: ((int;int);int) + +\: adder_literal2 (int n) { addn; } // addn will bind the local "n" + +assert(addn(2) == 12); +assert(adder(11)(9) == 20); +assert(adder(10)(9) == 19); +assert(adder_literal(3)(10) == adder(3)(10)); +assert(adder_literal2(4)(3) == adder_literal(4)(3)); + +// +// Something a bit more freaky. In this case, n is doubled inside the +// function locally. Since the addn lambda expression occurs *after* the +// doubling, the addn function binds 2 * n instead of n. +// +// adder_double signature is: ((int;int);int) + +\: adder_double (int n) { n *= 2; addn; } + +assert(adder_double(2)(2) == 6); + +// +// Sanity check. addn will use the global value 10 in this case +// + +// \: adder_10 (int q) { addn; } +// adder_10(123); + + +// +// Here's function composition (as in compose.mu) using closures +// +// F1 is a type of "takes a float returns a float" +// F2 is a type of "takes two floats returns a float" +// +// compose signatures: ((float;float);(float;float)) +// ((float;float,float);(float;float),(float;float,float)) + +F1 := (float;float); +F2 := (float;float,float); + +\: compose (F1 a) { \: (float x) { a(x); }; } +\: compose (F1 a, F2 b) { \: (float x, float y) { b(a(x), y); }; } + +assert(compose(math.sin)(.123) == math.sin(.123)); +assert(compose(math.sin, math.atan2)(.123, .321) == + math.atan2(math.sin(.123), .321)); + +compose(math.sin, math.atan2); + +// +// Why is this interesting? +// +// quadruple actually contains a free variable (double) +// which is bound to the outside double when quadruple(2) +// is called! So it actually computes the correct value +// (as if this were ML). +// + +let doubleIt = \: (int x) { x + x; }; +let quadrupleIt = \: (int x) { doubleIt(x); }; +quadrupleIt(2); + +print(string(quadrupleIt) + "\n"); diff --git a/src/bin/mu/mu-interp/test/function.mu b/src/bin/mu/mu-interp/test/function.mu new file mode 100644 index 000000000..794079913 --- /dev/null +++ b/src/bin/mu/mu-interp/test/function.mu @@ -0,0 +1,91 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// Test function definitions +// + +function: logN (float; float x, float base) +{ + math.log(x) / math.log(base); +}; + +function: logNCheck (bool;) +{ + logN(2.0, 3.0) == math.log(2.0) / math.log(3.0); +} + +assert( logNCheck() ); + +function: identity (float; float a) { a; } + +float q = 4.0; + +assert(identity(q) == 4.0); + +function: sum (vector float[4]; + vector float[4] a, + vector float[4] b, + vector float[4] c, + vector float[4] d) +{ + a + b + c + d; +} + +vector float[4] v0 = vector float[4](1,0,0,0); +vector float[4] v1 = vector float[4](0,1,0,0); +vector float[4] v2 = vector float[4](0,0,1,0); +vector float[4] v3 = vector float[4](0,0,0,1); + +assert( sum(v0, v1, v2, v3) == vector float[4](1,1,1,1) ); + +// +// Recursion +// + +function: factorial (int; int x) +{ + if x == 1 then 1 else x * factorial(x-1); +} + +assert( factorial(10) == (10 * 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2) ); + +// +// function in the scope of another function +// + +function: foo (float; float x, float y, float z) +{ + function: bar(float; float x, float y) { x / y; } + bar(x, y) * bar(x, z); +} + +assert( foo(11.0, 22.0, 44.0) == ((11.0 / 22.0) * (11.0 / 44.0)) ); + +// +// Recursive function calling recursive function +// + +function: foo (float; float x) +{ + if x == 2 then foo(1.0, 2.0, 3.0) + foo(x - 1.0) else factorial(5); +} + +assert( int(foo(2.0)) == 120 ); + +// +// Unspecified return type forms +// + +function: unspecifiedReturn_logN (float x, float base) +{ + math.log(x) / math.log(base); +}; + +function: unspecifiedReturn_logNCheck () +{ + unspecifiedReturn_logN(2.0, 3.0) == math.log(2.0) / math.log(3.0); +} diff --git a/src/bin/mu/mu-interp/test/function_order.mu b/src/bin/mu/mu-interp/test/function_order.mu new file mode 100644 index 000000000..27a3a6d68 --- /dev/null +++ b/src/bin/mu/mu-interp/test/function_order.mu @@ -0,0 +1,16 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// The second function should override the first +// + +class: x +{ +method: foo (bool;) { return false; } + +//method: foo (bool;) { return true; } +} diff --git a/src/bin/mu/mu-interp/test/function_type.mu b/src/bin/mu/mu-interp/test/function_type.mu new file mode 100644 index 000000000..bd63f38c4 --- /dev/null +++ b/src/bin/mu/mu-interp/test/function_type.mu @@ -0,0 +1,86 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +require math; +require math_util; + +// +// Parse some obnoxious function type declarations +// + +poop := ((float; vector float[3]); (float; float), ((float; int); int)); +poop2 := ((float[]; vector float[3]); (float; float), ((float; int); int)); + +(void; float) foo = print; +print(foo); print("\n"); + +\: sprint (string; string s) { s + " world"; } +\: foobar (void;) { print("foobar\n"); } + +(string; string) x = sprint; +(float; float) s = math.sin; +(void;) v = foobar; +(float; float, float) r = math_util.random; + + +assert( x("hello") == "hello world" ); +assert( s(math.pi / 2.0) == math.sin(math.pi / 2.0) ); +v(); +math_util.seed(10); +float rr = r(1.0, 2.0); +math_util.seed(10); +float rn = math_util.random(1.0, 2.0); +assert(rr == rn); + +// +// Try a little functional programming. Its in its own module in +// order to protect it from future primitive functions "map" and +// "fold" +// + +module: test +{ + \: add (int; int a, int b) { a + b; } + \: times (int; int a, int b) { a * b; } + \: sub (int; int a, int b) { a - b; } + + \: fold (int; (int;int,int) f, [int] a, int i) + { + let ac = i; + for_each (e; a) { ac = f(e, ac); } + ac; + } + + let list = [1, 2, 3, 4]; + + assert(fold(add, list, 0) == 10); + assert(fold(times, list, 1) == 24); + assert(fold(sub, list, 0) == 2); + + \: println (void; string s) { print(s + "\n"); } + \: map (void; (void;string) f, [string] a) { for_each (e; a) f(e); } + + map(println, ["Hello", "World"]); + + // + // Ok let's get crazy + // + + let flist = [add, times, sub]; + [int] ac; + for_each (f; flist) ac = fold(f, list, 0) : ac; + fold(add, ac, 0); + + // + // Lambda expressions + // + + let F = \: (float; float a, float b) { (a + b) / (a - b); }; + assert( F(3, 7) == -2.5 ); + + map( \: (void; string s) { print("--> %s <--\n" % s); }, + [ "Hello", "World", "using", "map" ] ); +} + diff --git a/src/bin/mu/mu-interp/test/garbage.mu b/src/bin/mu/mu-interp/test/garbage.mu new file mode 100644 index 000000000..74053b389 --- /dev/null +++ b/src/bin/mu/mu-interp/test/garbage.mu @@ -0,0 +1,60 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// jn 10, in 100000 +// grits opt 12/18/2002: 221.340u 0.000s 3:41.38 99.9% 0+0k 0+0io 313pf+0w +// (unsafe, no 1-bit ref, setjmp always) +// grits opt 12/31/2002: 242.640u 0.000s 4:02.65 99.9% 0+0k 0+0io 316pf+0w +// (safe, no 1-bit ref, setjmp always) + + +// jn 10, in 10000, 65000 +// 101.540u 0.400s 1:58.04 86.3% 0+0k 0+0io 0pf+0w +// 6500 +// 106.560u 1.110s 2:04.31 86.6% 0+0k 0+3io 0pf+0w + +use runtime; +use math_util; + +// \: gcc (void;) +// { +// print("gc: " + +// "collected " + gc_collected_objects() + +// ", scanned " + gc_scanned_objects() + "\n"); +// } + +//runtime.set_collect_threshold(6500); +//runtime.set_aux_collect_threshold(1024 * 11); +// runtime.call_on_collect(gcc); + +module: garbage { + +int jn = 70; +int in = 100; + + \: test (void; int jn=7, int in=100) + { + + for (int j=0; j < jn; j++) + { + int delta = in / 2; + //in += delta / 2 - math_util.random(delta); + + for (int i=0; i < in; i++) + { + string s = "i = " + i + "\n"; + string t = s + s + s + s + s + s + s + s + s + s + s + s + s + s + s; + + for (int q=1; q < t.size(); q++) + { + string y = t.substr(0,q); + } + } + } + } + +} + +garbage.test(10,700); diff --git a/src/bin/mu/mu-interp/test/global.mu b/src/bin/mu/mu-interp/test/global.mu new file mode 100644 index 000000000..922e1a74b --- /dev/null +++ b/src/bin/mu/mu-interp/test/global.mu @@ -0,0 +1,24 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +global float a = 10.0; +global string s = "hello world\n"; + +function: print_a (float;) +{ + a = 123.3; + print(a + "\n"); + a; +} + +function: print_s (string;) +{ + print(s + "\n"); + s; +} + +assert( print_a() == a ); +assert( print_s() == s ); diff --git a/src/bin/mu/mu-interp/test/half.mu b/src/bin/mu/mu-interp/test/half.mu new file mode 100644 index 000000000..b4546b951 --- /dev/null +++ b/src/bin/mu/mu-interp/test/half.mu @@ -0,0 +1,34 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +module: suffix { \: h (half; float f) { half(f); } } + +let v = 1.0h + 2.0h; + +assert(v == 3.0h); +print("value is %s\n" % string((v, "h"))); +print("value is %.1f\n" % v); + +half[] array; +half[4,4] M = {1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 }; + +for (int i=0; i< 100; i++) array.push_back(half(i)); +assert(array[50] == 50.0h); +for_each (h; array) print(" %g" % h); +print("\n"); + +let n = half(1.0 + 1.1 + 1.02 + 1.003 + 1.0004); + +// some half specific functions are in the scope of half +n.round(3); +n.bits(); +let q = 123h; +assert(q.bits() == 30075); +half.epsilon; +half.infinity; diff --git a/src/bin/mu/mu-interp/test/if.mu b/src/bin/mu/mu-interp/test/if.mu new file mode 100644 index 000000000..5841952f4 --- /dev/null +++ b/src/bin/mu/mu-interp/test/if.mu @@ -0,0 +1,27 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +float i = 1.23; + +if (true) +{ + int q = math.max(1,2); + i *= 100; + + if (true) + { + int i = 10; + assert(i == 10); + } + else + { + assert(false); + int c = 1; + } + + assert(i == 123.0); + q += 2; +} diff --git a/src/bin/mu/mu-interp/test/image.mu b/src/bin/mu/mu-interp/test/image.mu new file mode 100644 index 000000000..b1f862b13 --- /dev/null +++ b/src/bin/mu/mu-interp/test/image.mu @@ -0,0 +1,111 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +Color := vector float[4]; +AttributePair := (string,string); +Attributes := [AttributePair]; + +union: Pixels +{ + FloatPixels float[] | + HalfPixels half[] | + ShortPixels short[] | + BytePixels byte[] +} + + +class: ImagePlane +{ + ChannelNames := string[]; + + Pixels _pixels; + int _width; + int _height; + int _scanlineSize; + int _depth; + ChannelNames _channelNames; + + method: index (int; float ndcx, float ndcy) + { + let x = int(ndcx * float(_width)), + y = int(ndcy * float(_height)); + + y * _scanlineSize + x * _channelNames.size(); + } + + method: channelValue (float; int ch, float ndcx, float ndcy) + { + let i = index(ndcx, ndcy) + ch; + + case (_pixels) + { + FloatPixels p -> { return p[i]; } + HalfPixels p -> { return float(p[i]); } + ShortPixels p -> { return float(p[i]); } + BytePixels p -> { return float(p[i]) / 255.0; } + } + } +} + +ImagePlanes := ImagePlane[]; +EncodedPlanes := (string, byte[])[]; + +class: Image +{ + Attributes _attributes; + ImagePlanes _planes; +} + +class: EncodedImage +{ + Attributes _attributes; + EncodedPlanes _planes; +} + +class: RGBASampler +{ + Channel := (ImagePlane, int); + + Channel _R; + Channel _G; + Channel _B; + Channel _A; + Channel _Y; + Image _image; + + method: RGBASampler (RGBASampler; Image i) + { + _image = i; + + for_each (p; i._planes) + for_index (i; p._channelNames) + { + let n = p._channelNames[i], + c = (p, i); + + if (n == "R") _R = c; + else if (n == "G") _G = c; + else if (n == "B") _B = c; + else if (n == "A") _A = c; + else if (n == "Y") _Y = c; + } + + assert(_R neq nil || _Y neq nil); + if (_R neq nil) assert(_B neq nil); + } + + operator: [] (Color; RGBASampler this, float ndcx, float ndcy) + { + if (_G neq nil) + { + return Color(_R._0.channelValue(ndcx, ndcy, _R._1), + _G._0.channelValue(ndcx, ndcy, _G._1), + _B._0.channelValue(ndcx, ndcy, _B._1), + _A._0.channelValue(ndcx, ndcy, _A._1)); + } + } +} + + diff --git a/src/bin/mu/mu-interp/test/imath.mu b/src/bin/mu/mu-interp/test/imath.mu new file mode 100644 index 000000000..6503b578f --- /dev/null +++ b/src/bin/mu/mu-interp/test/imath.mu @@ -0,0 +1,27 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// This is a module (imath) defined purely as source +// no .muc or .so file. +// +// The module will fail to load if a module of the same +// name is not defined in here. +// + +module: imath +{ + use math; + \: add (int; int a, int b) { a + b; } + \: subtract (int; int a, int b) { a - b; } + \: multiply (int; int a, int b) { a * b; } + \: divide (int; int a, int b) { a / b; } + \: modulo (int; int a, int b) { a % b; } + \: power (int; int a, int b) { math.pow(a, b); } + + global int answer = 42; +} + diff --git a/src/bin/mu/mu-interp/test/int64.mu b/src/bin/mu/mu-interp/test/int64.mu new file mode 100644 index 000000000..d75ac0808 --- /dev/null +++ b/src/bin/mu/mu-interp/test/int64.mu @@ -0,0 +1,38 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +for_each (m; runtime.machine_types()) +{ + if (m._0 == "int64") + print("name=(%s %s) size=%d width=%d salign=%d nalign=%d\n" % m); +} + +module: suffix +{ + \: L (int64; int a) { a; } + \: L (int64; int64 a) { a; } + \: I (int; int a) { a; } +} + +let x = 123L, + y = int(x), + z = int64(x), + m = -9223372036854775807; // must be 64 + +"foo " + int64.min; + +// too big for 32bits, must be 64 +print("9223372036854775807 = %d\n" % 9223372036854775807); +// format conversion should be automatic based on arg types +print("int64.max padded 20 = \"%20x\"\n" % int64.max); +print("int64.min padded 20 = \"%20x\"\n" % int64.min); +print("int64.max = %d\n" % int64.max); +print("float(int64.max) = %f\n" % float(int64.max)); +print("sum = %d\n" % (int64.min + int64.max)); + +int64[4,4] M = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1}; +let l = [1L, 2L, 3L]; +let a = int64[] {10, 20, int64.max, int64.min}; diff --git a/src/bin/mu/mu-interp/test/kernel.mu b/src/bin/mu/mu-interp/test/kernel.mu new file mode 100644 index 000000000..18829aa2e --- /dev/null +++ b/src/bin/mu/mu-interp/test/kernel.mu @@ -0,0 +1,39 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// Compute a gaussian kernel +// + +int n = 5; +float[5,5] kernel; +float accum = 0.0; + +\: g (float x) { math.exp(-(x * x)); } + +for (int i=0; i < n; i++) +{ + for (int j=0; j < n; j++) + { + let xd = float(i) / (n - 1.0) - 0.5, + yd = float(j) / (n - 1.0) - 0.5, + d = math.sqrt(xd * xd + yd * yd), + ga = g(d * 2.5); + + kernel[i,j] = ga; + accum += ga; + } +} + +for (int i=0; i < n; i++) +{ + for (int j=0; j < n; j++) + { + kernel[i,j] /= accum; + } +} + +kernel; diff --git a/src/bin/mu/mu-interp/test/key_to_string_map.mu b/src/bin/mu/mu-interp/test/key_to_string_map.mu new file mode 100644 index 000000000..806a71a5c --- /dev/null +++ b/src/bin/mu/mu-interp/test/key_to_string_map.mu @@ -0,0 +1,119 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +module: key_to_string_map +{ + global int[] primes = + { 7, 13, 23, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593, + 49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469, + 12582917, 25165843 }; + + \: nextPrime (int; int p) + { + for_index (i; primes) if (primes[i] > p) return primes[i]; + return primes.back(); + } + + KeyType := int; + ValueType := string; + + documentation: """ + A basic hash map from int -> string + """; + + class: HashTable + { + class: Item + { + KeyType name; + ValueType value; + Item next; + } + + Item[] _table; + int _numItems; + + method: hashF (KeyType; KeyType i) { return i*2654435761; } + + method: _addInternal (void; KeyType name, ValueType value) + { + let q = hashF(name) % _table.size(); + _table[q] = Item(name, value, _table[q]); + _numItems++; + } + + method: resize (void;) + { + let newSize = nextPrime(_table.size()), + oldTable = _table; + + _table = Item[](); + _table.resize(newSize); + + for_each (item; oldTable) + { + for (Item i = item; i neq nil; i = i.next) + { + _addInternal(i.name, i.value); + } + } + } + + method: HashTable (HashTable; int initialSize) + { + _table = Item[](); + _table.resize(nextPrime(initialSize)); + resize(); + } + + method: add (void; KeyType name, ValueType value) + { + _addInternal(name, value); + if (_numItems > _table.size() * 2) resize(); + } + + method: allItems (Item[];) + { + Item[] items; + + for_each (item; _table) + { + while (item neq nil) + { + items.push_back(item); + item = item.next; + } + } + + return items; + } + + method: keys (KeyType[];) + { + KeyType[] array; + for_each (i; allItems()) array.push_back(i.name); + array; + } + + method: find (ValueType; KeyType name) + { + let i = hashF(name) % _table.size(); + + for (Item x = _table[i]; x neq nil; x = x.next) + { + if (x.name == name) return x.value; + } + + return nil; + } + } + +} + +//use key_to_string_map; +//let t = HashTable(13); +//t.add(123, "foo bar"); +//t.find(123); diff --git a/src/bin/mu/mu-interp/test/let.mu b/src/bin/mu/mu-interp/test/let.mu new file mode 100644 index 000000000..21408df4e --- /dev/null +++ b/src/bin/mu/mu-interp/test/let.mu @@ -0,0 +1,72 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +function: equivalent (bool; string[] a, string[] b) +{ + if (a.size() != b.size()) return false; + for (int i=0; i < a.size(); i++) + if (a[i] != b[i]) return false; + true; +} + +require math; + +let x = 10.0; // x => float +let s = math.sin(x); // s => float +let y = 10; // y => int + + +string foo = "foo|bar"; +let tokens = string.split(foo, "|"); +assert(equivalent(tokens, string[] {"foo", "bar"})); + +// +// comma seperated let declares each variable using its own rhs +// + +let a = 10 % 6, // a => int + b = math.cos(math.pi), // b => float + c = "chicken"; // c => string + +assert(c == "chicken"); +assert(a == 4); +assert(b == math.cos(math.pi)); + + +// +// Unresolved implicit typing +// The types of local variables may require patching +// + +function: foo (int; int a) +{ + let x = bar(a, 10), + y = float(bar(a, 11)); + + x + y; +} + +function: bar (int; int a, int b) +{ + a + b; +} + +assert(foo(11) == 43); + +// +// Pattern matching +// + + +{ + class: Foo { float a; (float,float) b; } + + let pi = float(math.pi); + + let (a, {b, (c0, c1)}, (d, e, f)) = (1, Foo(2.0, (3.0, pi)), (9, 8, 7)); + + assert(c1 == pi); +} + diff --git a/src/bin/mu/mu-interp/test/list.mu b/src/bin/mu/mu-interp/test/list.mu new file mode 100644 index 000000000..65a36e650 --- /dev/null +++ b/src/bin/mu/mu-interp/test/list.mu @@ -0,0 +1,78 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +Vec := vector float[3]; + +\: test (void;) +{ + let a = [int] {1, 2, 3, 4, 5, 6}, + b = cons(-3, cons(-2, cons(-1, a))), + c = [1,2,3,4], + d = [a, c, b], + f = [[1,2], [3,4,5]], + g = [[[1],[2]], [[3],[4],[5]]]; + + + let [q,r,s,t] = c; + let [[a0, a1], [b0, b1, b2]] = f; + + assert(q == 1); + assert(r == 2); + assert(s == 3); + assert(t == 4); + + assert(a0 == 1 && + a1 == 2 && + b0 == 3 && + b1 == 4 && + b2 == 5); + + for_each (i; a) print(i); + + assert(string(a) == "[1, 2, 3, 4, 5, 6]"); + assert(string(b) == "[-3, -2, -1, 1, 2, 3, 4, 5, 6]"); + assert(string(c) == "[1, 2, 3, 4]"); + assert(string(d) == "[[1, 2, 3, 4, 5, 6], [1, 2, 3, 4], [-3, -2, -1, 1, 2, 3, 4, 5, 6]]"); + assert(string(f) == "[[1, 2], [3, 4, 5]]"); + assert(string(g) == "[[[1], [2]], [[3], [4], [5]]]"); + + print("a = %s\n" % a); + print("b = %s\n" % b); + print("c = %s\n" % c); + print("d = %s\n" % d); + print("f = %s\n" % f); + print("g = %s\n" % g); + print("tail(a) = %s\n" % tail(a)); + print("head(a) = %d\n" % head(a)); + print("%s\n" % cons(1,nil)); + + let v = [Vec(1), Vec(2), Vec(3)]; + + [int] l; + for (int i=0; i < 1000000; i++) l = i : l; + + // + // This is to make sure the grammar allows this type of + // expression. The list literal has to be a primary_expression + // for this type of thing to work. + // + + operator: + ([int]; [int] list, (int,int,int) tuple) + { + let [a,b,c] = list, + (x,y,z) = tuple; + + return [a+x, b+y, c+z]; + } + + { + let [a,b,c] = [1,2,3] + (1,2,3); + assert(a == 2); + assert(b == 4); + assert(c == 6); + } +} + +test(); diff --git a/src/bin/mu/mu-interp/test/list_class.mu b/src/bin/mu/mu-interp/test/list_class.mu new file mode 100644 index 000000000..694d58b59 --- /dev/null +++ b/src/bin/mu/mu-interp/test/list_class.mu @@ -0,0 +1,116 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +Value := int; + +documentation: +"An STL list<> like class. +functions allow self-updating (not a functional list)."; + +class: List +{ + documentation: + "Cell class holds individual list items."; + + class: Cell + { + Value value; + Cell next; + } + + Cell start, end; + + method: List (List; Value v) + { + start = List.Cell(v, nil); + end = start; + } + + function: size (int; List this) + { + int n = 0; + for (Cell c = start; !(c eq nil); c = c.next) n++; + n; + } + + function: size_recursive (int; List this) + { + function: f (int; Cell c, int n) { if c eq nil then n else f(c.next, n+1); } + f(this.start, 0); + } + + function: push_back (void; List this, Value v) + { + end.next = Cell(v, nil); + end = end.next; + } + + function: push_back (void; List this, List other) + { + end.next = other.start; + end = other.end; + } + + function: push_front (void; List this, Value v) + { + Cell c = Cell(v, start); + start = c; + } + + function: push_front (void; List this, List other) + { + other.end.next = start; + start = other.start; + } + + function: front (Value; List this) { start.value; } + function: back (Value; List this) { end.value; } + + function: pop_front (Value; List this) + { + Cell c = start; + if (!(c eq nil)) start = start.next; + c.value; + } + + operator: [] (Value; List this, int index) + { + function: f (Value; Cell c, int i, int index) + { + if (c eq nil) + { + throw "bad"; + } + else + { + return if i == index then c.value else f(c.next, i+1, index); + } + } + + f(start, 0, index); + } +} + + +List l1 = List(1); +for (int i=2; i < 10; i++) l1.push_back(i); + +List l2 = List(100); +for (int i=101; i < 200; i++) l2.push_back(i); + +l1.push_back(l2); +assert(l1.size() == l1.size_recursive()); +assert(l1[8] == 9); + +try +{ + l1[500]; + assert(false); +} +catch (...) +{ + print("caught it\n"); +} + diff --git a/src/bin/mu/mu-interp/test/local_var.mu b/src/bin/mu/mu-interp/test/local_var.mu new file mode 100644 index 000000000..2013c13bc --- /dev/null +++ b/src/bin/mu/mu-interp/test/local_var.mu @@ -0,0 +1,7 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +int a,b,c; +1 + 2; diff --git a/src/bin/mu/mu-interp/test/lower_bounds.mu b/src/bin/mu/mu-interp/test/lower_bounds.mu new file mode 100644 index 000000000..0f09eace0 --- /dev/null +++ b/src/bin/mu/mu-interp/test/lower_bounds.mu @@ -0,0 +1,30 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +\: lower_bounds (int; int[] array, int n) +{ + \: f (int; int[] array, int n, int i, int i0, int i1) + { + if (array[i] <= n) + { + if (i+1 == array.size() || array[i+1] > n) + { + return i; + } + else + { + return f(array, n, (i + i1) / 2, i, i1); + } + } + + if i == 0 then -1 else f(array, n, (i + i0) / 2, i0, i); + } + + f(array, n, array.size() / 2, 0, array.size()); +} + +int[] array = int[] {10, 20, 30, 40, 50, 60, 70, 80, 90}; +let index = lower_bounds(array, 81); +assert(index == 7); diff --git a/src/bin/mu/mu-interp/test/main.mu b/src/bin/mu/mu-interp/test/main.mu new file mode 100644 index 000000000..1a5e9a000 --- /dev/null +++ b/src/bin/mu/mu-interp/test/main.mu @@ -0,0 +1,24 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +use math; + +// +// to get this file to work, you need to specificy -main +// on the mu-interp command line. +// + +use math; // test declaration scope + +function: main(void; string[] args) +{ + print("hello world!\n"); + + for (int i=0; i < args.size(); i++) + { + print(args[i] + " "); + } + print("\n"); +} diff --git a/src/bin/mu/mu-interp/test/mandelbrot.mu b/src/bin/mu/mu-interp/test/mandelbrot.mu new file mode 100644 index 000000000..3899c1592 --- /dev/null +++ b/src/bin/mu/mu-interp/test/mandelbrot.mu @@ -0,0 +1,51 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +BAILOUT := 16; +MAX_ITERATIONS := 1000; + + + +\: mandelbrot(int; float x, float y) +{ + let cr = y - 0.5, + ci = x, + zi = 0.0, + zr = 0.0; + + for (int i=0; i <= MAX_ITERATIONS; i++) + { + let temp = zr * zi, + zr2 = zr * zr, + zi2 = zi * zi; + + zr = zr2 - zi2 + cr; + zi = temp + temp + ci; + + if ((zi2 + zr2) > BAILOUT) return i; + } + + return 0; +} + + +\: doit (void;) +{ + for (float y = -39.0; y < 39; y += 1.0) + { + print("\n"); + + for (float x = -39.0; x < 39; x += 1.0) + { + let i = mandelbrot(x/40.0, y/40.0); + + if (i == 0) print("*"); + else print(" "); + } + } +} + +doit(); diff --git a/src/bin/mu/mu-interp/test/mandelbrot.py b/src/bin/mu/mu-interp/test/mandelbrot.py new file mode 100644 index 000000000..f3534a3ec --- /dev/null +++ b/src/bin/mu/mu-interp/test/mandelbrot.py @@ -0,0 +1,53 @@ +#!/usr/bin/python +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# by Daniel Rosengren + +import sys, time + +stdout = sys.stdout + +BAILOUT = 16 +MAX_ITERATIONS = 1000 + + +class Iterator: + def __init__(self): + print("Rendering...") + for y in range(-39, 39): + stdout.write("\n") + for x in range(-39, 39): + i = self.mandelbrot(x / 40.0, y / 40.0) + + if i == 0: + stdout.write("*") + else: + stdout.write(" ") + + def mandelbrot(self, x, y): + cr = y - 0.5 + ci = x + zi = 0.0 + zr = 0.0 + i = 0 + + while True: + i += 1 + temp = zr * zi + zr2 = zr * zr + zi2 = zi * zi + zr = zr2 - zi2 + cr + zi = temp + temp + ci + + if zi2 + zr2 > BAILOUT: + return i + if i > MAX_ITERATIONS: + return 0 + + +t = time.time() +Iterator() +print("Python Elapsed %.02f" % (time.time() - t)) diff --git a/src/bin/mu/mu-interp/test/math.mu b/src/bin/mu/mu-interp/test/math.mu new file mode 100644 index 000000000..c44a64f90 --- /dev/null +++ b/src/bin/mu/mu-interp/test/math.mu @@ -0,0 +1,20 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +use math; +use math_util; +v3 := vector float[3]; +v2 := vector float[2]; + +assert( smoothstep(0, 1, .25) == 0.15625 ); +assert( linstep(0, 1, .25) == .25 ); +assert( cbrt(8.0) == 2.0 ); +assert( hypot(5.0, 12.0) == 13.0 ); +assert( math.abs(acos(0) * 2.0 - math.pi) < 0.00001 ); + +noise(1.1); +noise(v2(1.1,2.2)); +noise(v3(1.2,8.2,3.3)); diff --git a/src/bin/mu/mu-interp/test/max.mu b/src/bin/mu/mu-interp/test/max.mu new file mode 100644 index 000000000..ef8849056 --- /dev/null +++ b/src/bin/mu/mu-interp/test/max.mu @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +assert(math.max(1,2.) == 2.) diff --git a/src/bin/mu/mu-interp/test/membervar.mu b/src/bin/mu/mu-interp/test/membervar.mu new file mode 100644 index 000000000..c59e681e0 --- /dev/null +++ b/src/bin/mu/mu-interp/test/membervar.mu @@ -0,0 +1,15 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +vector float[4] v = vector float[4](1,2,3,4); +print("v = %s\n" % v); +print("v.z = %s\n" % v.z); +float f = (v.x + v.y + v.z + v.w) / float(4.0); +v.x = f; +v.w = (vector float[4](0,0,0,123) + vector float[4](0,0,0,200)).w; +print("f = %s\n" % f); +assert(f == 2.5); +assert(v == vector float[4](2.5, 2, 3, 323)); diff --git a/src/bin/mu/mu-interp/test/method_scope.mu b/src/bin/mu/mu-interp/test/method_scope.mu new file mode 100644 index 000000000..a28cfe25a --- /dev/null +++ b/src/bin/mu/mu-interp/test/method_scope.mu @@ -0,0 +1,90 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +class: X +{ + method: f (int;) { this.g(); } + method: g (int;) { 911; } +} + +class: Base +{ + int q; + + method: Base (Base;) + { + q = 9; + x = 99.1; // will require a cast + + // BELOW fails, "x" and "q" are a free variables which should + // resolve (eventually) to the fields. In the first case it + // fails to even see "x". In the second "q" is seen, but can't + // be passed in for some reason. + + //Q = \: (int;int a) { a + x; }; + //Q = \: (int;int a) { a + q; }; + + // should fail + //Q = \: (float; int a) { a + 1; }; + + Q = \: (int; int a) { a + 1; }; + } + + \: ff (void; Base B, int a) { ; } + \: ff (void; Base B, int a, int b) { ; } + + method: f (int; int a, int b) { this.g(a) + b; } + method: g (int; int a) { a; } + method: p ((int;int);) { g; } + + (int;int) Q; + int x; +} + +class: Derived : Base +{ + method: Derived (Derived;) + { + x = 999; + y = 888; + Q = \: (int; int a) { a * 2; }; + } + + \: ff (void; Derived d, string a) { ; } + + method: f (int; int a, int b) { this.g(a) - b; } + method: g (int; int a) { a + y; } + method: p ((int;int);) { f(7,); } + + int y; +} + +\: foo (void;) +{ + Base b = Derived(); + + let G = b.g, + F = b.f(1,), + BaseG = Base.g, + DervG = Derived.g; + + assert(G(123) == 888 + 123); + assert(F(2) == 1 + 888 - 2); + assert(Base.g(b, 2) == 2); + assert(b.Q(321) == 321 * 2); + assert(b.f(2,3) == (2+888) - 3); + assert(BaseG(b, 1) == 1); + assert(DervG(b, 1) == 1 + 888); + assert(b.p()(77) == 7 + 888 - 77); + assert(Base.p(b)(77) == b.g(77)); + assert((X().f)() == 911); + + Derived().ff("a"); + Derived().ff(1,2); +} + +foo(); + diff --git a/src/bin/mu/mu-interp/test/module.mu b/src/bin/mu/mu-interp/test/module.mu new file mode 100644 index 000000000..7a78c3bc2 --- /dev/null +++ b/src/bin/mu/mu-interp/test/module.mu @@ -0,0 +1,45 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +int x = 5; + +module: foo +{ + global int nine = 9; + function: bar (float;) { 123.321; } + function: something (void;) { print("what's up\n"); } + int x = 10; + assert(x == 10); + assert(nine == 9); +} + +assert(x == 5); +assert(foo.bar() == 123.321); +assert(foo.nine == 9); + +module: foo +{ + // + // Extend module foo + // + + function: twoTimesBar (float;) { bar() * 2.0; } + assert(nine == 9); +}; + +assert(foo.twoTimesBar() == foo.bar() * 2.0); + +// +// NOTE: if you try to extend module foo after the "use" statement, +// mu will make a foo.foo module not extend __root.foo as you might +// expect! +// + +use foo; + +assert(nine == 9); +assert(bar() == 123.321); +assert(foo.bar() == bar()); + diff --git a/src/bin/mu/mu-interp/test/multiple.mu b/src/bin/mu/mu-interp/test/multiple.mu new file mode 100644 index 000000000..6ae702c72 --- /dev/null +++ b/src/bin/mu/mu-interp/test/multiple.mu @@ -0,0 +1,72 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// Multiple inheritance +// + +class: Bprime { int bp; } + +class: A { int a; } +class: B : Bprime { int b; int bb; } +class: C { int c; int cc;} +class: D : A, B, C { int d; } + +class: E { int e; } +class: F { int f; } +class: G { int g; } +class: H : E, F, G { int h; } + +class: J : H, D { int j; } + +union: JU { JUnion J }; +use JU; + +\: foo () +{ + // + // Make this hard by putting the class instance inside a union. The + // casting will need to sort this out + // + + JU ju = JUnion(11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22); + + let JUnion j = ju; // unpack the class instance + + + H h = j; print("h => %s\n" % h); // "upcast" + D d = j; print("d => %s\n" % d); // "upcast" to a D + B b = j; print("b => %s\n" % b); // "upcast" to a B + D nd = b; print("nd => %s\n" % nd); // "downcast" back to D + J nj = b; print("nj => %s\n" % nj); // "downcast" back to J + // NOT ALLOWED: G g = b; print("g = %s\n" % h); // "overcast" to G + G g = nj; print("g => %s\n" % g); // "upcast" back to G + + assert(h.h == 12); + assert(d.d == 17); + assert(b.b == 19); + assert(b.bp == 18); + assert(g.g == 14); +} + +class: Ae : A { ; } +class: Be : B { ; } +class: Ce : C { ; } +class: De : D { ; } +class: Ee : E { ; } +class: Fe : F { ; } +class: Ge : Ae, Be, Ce, De, Ee, Fe { ; } + +\: bar () +{ + Ae ae = Ge(); + Ge ge = ae; + Be be = ge; + Fe fe = ge; + be; +} + +foo(); +bar(); diff --git a/src/bin/mu/mu-interp/test/nbody.mu b/src/bin/mu/mu-interp/test/nbody.mu new file mode 100644 index 000000000..ae3205167 --- /dev/null +++ b/src/bin/mu/mu-interp/test/nbody.mu @@ -0,0 +1,114 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +/* + * The Great Computer Language Shootout + * http://shootout.alioth.debian.org/ + * + * contributed by Christoph Bauer + * + */ + +use math; +use math_util; + +Vec := vector float[3]; + +global let SolarMass = 4.0 * float(math.pi) * float(math.pi), + DaysPerYear = 365.24; + +class: Planet +{ + Vec pos; + Vec vel; + float mass; +} + +\: advance (Planet[] bodies, float dt) +{ + let nbodies = bodies.size(); + + for (int i = 0; i < nbodies; i++) + { + Planet b = bodies[i]; + + for (int j = i + 1; j < nbodies; j++) + { + let b2 = bodies[j], + dp = b.pos - b2.pos, + d = mag(dp), + m = dt / (d * d * d); + + b.vel -= dp * b2.mass * m; + b2.vel += dp * b.mass * m; + } + } + + for_each (b; bodies) b.pos += dt * b.vel; +} + +\: energy (float; Planet[] bodies) +{ + let en = 0.0, + nbodies = bodies.size(); + + for (int i = 0; i < nbodies; i++) + { + let b = bodies[i]; + en += 0.5 * b.mass * dot(b.vel, b.vel); + + for (int j = i + 1; j < nbodies; j++) + { + let b2 = bodies[j], + dp = b.pos - b2.pos; + + en -= (b.mass * b2.mass) / mag(dp); + } + } + + en; +} + +\: offset_momentum (Planet[] bodies) +{ + let p = Vec(0.0); + for_each (b; bodies) p += b.vel * b.mass; + bodies[0].vel = -p / SolarMass; +} + +global Planet[] bodies = +{ + { /* sun */ + {0.0, 0.0, 0.0}, {0.0, 0.0, 0.0}, SolarMass + }, + { /* jupiter */ + {4.84143144246472090e+00, -1.16032004402742839e+00, -1.03622044471123109e-01}, + Vec(1.66007664274403694e-03, 7.69901118419740425e-03, -6.90460016972063023e-05) * DaysPerYear, + 9.54791938424326609e-04 * SolarMass + }, + { /* saturn */ + {8.34336671824457987e+00, 4.12479856412430479e+00, -4.03523417114321381e-01}, + Vec(-2.76742510726862411e-03, 4.99852801234917238e-03, 2.30417297573763929e-05) * DaysPerYear, + 2.85885980666130812e-04 * SolarMass + }, + { /* uranus */ + {1.28943695621391310e+01, -1.51111514016986312e+01, -2.23307578892655734e-01}, + Vec(2.96460137564761618e-03, 2.37847173959480950e-03, -2.96589568540237556e-05) * DaysPerYear, + 4.36624404335156298e-05 * SolarMass + }, + { /* neptune */ + {1.53796971148509165e+01, -2.59193146099879641e+01, 1.79258772950371181e-01}, + Vec(2.68067772490389322e-03, 1.62824170038242295e-03, -9.51592254519715870e-05) * DaysPerYear, + 5.15138902046611451e-05 * SolarMass + } +}; + +\: start (int n) +{ + offset_momentum(bodies); print("%.9f\n" % energy(bodies)); + repeat (n) advance(bodies, 0.01); print("%.9f\n" % energy(bodies)); +} + +start(100); diff --git a/src/bin/mu/mu-interp/test/nil.mu b/src/bin/mu/mu-interp/test/nil.mu new file mode 100644 index 000000000..eeefa5830 --- /dev/null +++ b/src/bin/mu/mu-interp/test/nil.mu @@ -0,0 +1,39 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +string foo = nil; +assert(foo eq nil); +print(foo); print("\n"); +print(foo + "\n"); + +foo = "foo bar"; + +try +{ + foo.split(nil); +} +catch (exception e) +{ + // + // should be nil argument exception + // + + print(string(e) + "\n"); +} + +foo = nil; + +try +{ + foo.split(" "); +} +catch (exception e) +{ + // + // should be nil argument exception also + // + + print(string(e) + "\n"); +} diff --git a/src/bin/mu/mu-interp/test/object.mu b/src/bin/mu/mu-interp/test/object.mu new file mode 100644 index 000000000..d25693ac5 --- /dev/null +++ b/src/bin/mu/mu-interp/test/object.mu @@ -0,0 +1,8 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +object x = "Hello"; +print(string(x) + "\n"); diff --git a/src/bin/mu/mu-interp/test/operator.mu b/src/bin/mu/mu-interp/test/operator.mu new file mode 100644 index 000000000..89ea47465 --- /dev/null +++ b/src/bin/mu/mu-interp/test/operator.mu @@ -0,0 +1,14 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +operator: * (string; string s, int n) +{ + string ns = string(s); + for (int i=1; i < n; i++) ns += s; + ns; +} + +assert( ("foo" * 4 + "\n") * 2 == "foofoofoofoo\nfoofoofoofoo\n"); diff --git a/src/bin/mu/mu-interp/test/operator_object.mu b/src/bin/mu/mu-interp/test/operator_object.mu new file mode 100644 index 000000000..61a6dce78 --- /dev/null +++ b/src/bin/mu/mu-interp/test/operator_object.mu @@ -0,0 +1,18 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// Test out the naked operator syntax +// + +let add = (+); +(int; int, int) iadd = add; +let add1 = iadd(1,); + +add1(2); + +assert((int;int,int)((+))(1,2) == 3); // explicit +//assert((+)(1,2) == 3); // implicit -- does not work yet diff --git a/src/bin/mu/mu-interp/test/partial.mu b/src/bin/mu/mu-interp/test/partial.mu new file mode 100644 index 000000000..a0a73886f --- /dev/null +++ b/src/bin/mu/mu-interp/test/partial.mu @@ -0,0 +1,23 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + + +//module: suffix { + //\: pi (void; float f) { math.pi * f; } +//} + +// \: foo () +// { +// string suffix = "foo"; +// } + +\: add (int; int a, int b) { a + b; } + +\: foo (int;) +{ + add(1,)(2); +} + diff --git a/src/bin/mu/mu-interp/test/partial_dynamic.mu b/src/bin/mu/mu-interp/test/partial_dynamic.mu new file mode 100644 index 000000000..3a4748838 --- /dev/null +++ b/src/bin/mu/mu-interp/test/partial_dynamic.mu @@ -0,0 +1,87 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// Test the partial application and partial evaluation as well as +// function object reference/creation. +// + + +// +// Use of a function object expression with partial apply and operator() +// + +\: f1 (float; float a, float b, float c) +{ + float i = b * c; + a + i; +} + +assert( f1(1,2,)(3) == f1(1,2,3) ); // direct +assert( (f1)(1,2,)(3) == f1(1,2,3) ); // indirect (always dynamic) + +// +// Use of variable containing function object w/ partial apply +// + +\: f2 (float; float a, float b, float c) +{ + float i = b * c; + return a + i; +} + +let f2v = f2; +assert( f2v(1,2,)(3) == f2(1,2,3) ); + +// +// Same as above, but with local variables in the function +// + +\: f3 (float; float a, float b, float c) +{ + { + float i = b; + c *= i; + } + + return a + c; +} + +let f3v = f3; +assert( f3v(1,2,)(3) == f3(1,2,3) ); + +f3v(1,2,); + +// +// This should evaluate via const reduction to: +// \: (int; int c) (+ 3 c) +// +\: add (int; int a, int b, int c) { a + b + c; } +let add3 = add(1,,)(2,); +assert(add3(4) == 7); + +// +// Reference type argumets with constant folding. Assert the actual +// contents of the partially applied function. +// + +\: sf (string; string a, string b, string c) +{ + a + "-" + b + "-" + c; +} + +assert(sf(,"a",)("oop", "doo") == sf("oop", "a", "doo")); // direct +assert((sf)(,"a",)("oop", "doo") == sf("oop", "a", "doo")); // indirect + + +// +// Harder -- a function which takes a function -- inserting a +// constant function object. +// + +\: func_a (string; int x, (string;int) f) { f(x); } +let qq = func_a(,\: (string;int x) { "X => " + x; }); +assert(qq(123) == "X => 123"); diff --git a/src/bin/mu/mu-interp/test/pattern.mu b/src/bin/mu/mu-interp/test/pattern.mu new file mode 100644 index 000000000..0a41aed72 --- /dev/null +++ b/src/bin/mu/mu-interp/test/pattern.mu @@ -0,0 +1,57 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +class: Foo { float a; float b; }; + +\: foo (float;) +{ + let list = [1,2,3,4]; + bool failed = false; + + try + { + let [q,r,s,t,y] = list; + } + catch (exception exc) + { + print("GOOD: it threw: %s\n" % string(exc)); + failed = true; + } + + assert(failed == true); + + let (a, ({b, c}, d)) = (1, (Foo(float(math.pi), float(math.e)), 2)), + {x,z} = Foo(3.321,4.321), + [l0, l1, l2, l3] = list, + [la, lb, lc] = list, + q:s:r:t = list; + + print("q=%s, s=%s, r=%s, t=%s\n" % (q,s,r,t)); + let tt:qq = t; + + assert(tt == 4); + + b; +} + +\: bar (void;) +{ + let ({a,b},c):[_,({e,f},_)] = [(Foo(1.0,2.0),(3.1,3.2)), + (Foo(4.0,5.0),(6.1,6.2)), + (Foo(4.9,5.9),(6.12,6.22))]; + + assert(a == 1.0 && + b == 2.0 && + c._0 == 3.1 && + c._1 == 3.2); + + assert(e == 4.9); + assert(f == 5.9); +} + +print("foo = %s\n" % foo()); +assert(foo() == float(math.pi)); +bar(); diff --git a/src/bin/mu/mu-interp/test/peer_closure.mu b/src/bin/mu/mu-interp/test/peer_closure.mu new file mode 100644 index 000000000..184972846 --- /dev/null +++ b/src/bin/mu/mu-interp/test/peer_closure.mu @@ -0,0 +1,16 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +\: foo (void;) +{ + let list = [1,2,3]; + \: bar (void;) { for_each (l; list) print(l); } + \: baz (void;) { bar(); } + + baz(); +} + +// should print "123" +foo(); diff --git a/src/bin/mu/mu-interp/test/pixel.mu b/src/bin/mu/mu-interp/test/pixel.mu new file mode 100644 index 000000000..0974b46ff --- /dev/null +++ b/src/bin/mu/mu-interp/test/pixel.mu @@ -0,0 +1,25 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// Test pixel rule for pushed image rotation +// + +//float angle = 10; +//vector center = <.5,.5>; +// +//float cosa = cos(angle); +//float sina = sin(angle); +// +//pixel() +//{ +// color = srcImage(position); +// vector dP = position - center; +// +// position = +// + center; +//} + diff --git a/src/bin/mu/mu-interp/test/point_tree.mu b/src/bin/mu/mu-interp/test/point_tree.mu new file mode 100644 index 000000000..a2e9e6f25 --- /dev/null +++ b/src/bin/mu/mu-interp/test/point_tree.mu @@ -0,0 +1,316 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +require math; + +module: point_tree +{ + +Vec := vector float[3]; +Scalar := float; +IndexArray := int[]; +PointArray := Vec[]; +SortFunc := (int; int, int); + +//---------------------------------------------------------------------- +// +// Slice +// + +class: Slice +{ + IndexArray _array; + int _start; + int _end; + + operator: [] (int; Slice this, int index) { _array[_start+index]; } + function: size (int; Slice this) { _end - _start; } + + method: Slice(Slice; IndexArray a, int start, int end) + { + _array = a; + _start = start; + _end = end; + } + + method: Slice (Slice; Slice other, int start, int end) + { + _array = other._array; + _start = other._start + start; + _end = other._start + start + (end - start); + } +} + + +//---------------------------------------------------------------------- +// +// nth_element +// for the moment just sort the whole array +// + +function: nth_sort(void; IndexArray a, int lo, int hi, int nth, SortFunc comp) +{ + if (lo < hi) + { + let l = lo, + h = hi, + p = a[hi]; + + do + { + while (l < h && comp(a[l], p) <= 0) l++; + while (h > l && comp(a[h], p) >= 0) h--; + + if (l < h) + { + let t = a[l]; + a[l] = a[h]; + a[h] = t; + } + + } while (l < h); + + let t = a[l]; + a[l] = a[hi]; + a[hi] = t; + + if (nth >= lo && nth <= l-1) nth_sort(a, lo, l-1, nth, comp); + else if (nth >= l+1 && nth <= hi) nth_sort(a, l+1, hi, nth, comp); + } +} + +function: nth_element (int; Slice slice, int nth, SortFunc comp) +{ + nth_sort(slice._array, slice._start, slice._end - 1, nth + slice._start, comp); + return slice[nth]; +} + +function: nth_element (int; IndexArray array, int nth, SortFunc comp) +{ + nth_sort(array, 0, array.size() - 1, nth, comp); + return array[nth]; +} + +//---------------------------------------------------------------------- +// +// BBox +// + +class: BBox +{ + Vec min; + Vec max; + + function: makeEmpty (void; BBox this) { min = float.max; max = -min; } + + function: extend (void; BBox this, Vec p) + { + if (p.x < min.x) min.x = p.x; if (p.x > max.x) max.x = p.x; + if (p.y < min.y) min.y = p.y; if (p.y > max.y) max.y = p.y; + if (p.z < min.z) min.z = p.z; if (p.z > max.z) max.z = p.z; + } + + function: majorAxis (int; BBox this) + { + Vec v = max - min; + if v.x > v.y then (if (v.x > v.z) then 0 else 2) else (if (v.y > v.z) then 1 else 2); + } + + function: center (Vec; BBox this) { (max + min) / 2.0; } + method: BBox (BBox;) { min = float.max; max = float.min; } + method: BBox (BBox; BBox other) { min = other.min; max = other.max; } +}; + + +//---------------------------------------------------------------------- +// +// Point Tree +// + +class: PointTree +{ + class: Node + { + Scalar _splitValue; + int _dimension; + BBox _box; + Node _right; + Node _left; + Slice _indices; + + function: hasLeft (bool; Node this) { return !(_left eq nil); } + function: hasRight (bool; Node this) { return !(_right eq nil); } + function: isLeaf (bool; Node this) { !this.hasLeft() && !this.hasRight(); } + + method: Node (Node;) { this; } + } + + CompareFunc := (int;int,int); + + Node _root; + BBox _bbox; + IndexArray _indices; + PointArray _points; + CompareFunc[3] _compFuncs; + + function: comp (int; int a, int b, int dim, PointArray points) + { + points[a][dim] - points[b][dim]; + } + + function: build (PointTree; PointTree this, PointArray points) + { + _points = points; + _bbox = BBox(); + _root = Node(); + _indices = IndexArray(); + _indices.resize(points.size()); + + for (int i=0; i < _indices.size(); i++) _indices[i] = i; + for_each (p; points) _bbox.extend(p); + + // curry the comp function + for_each (i; int[]{0,1,2}) _compFuncs[i] = comp(,,i,points); + + split(this, _root, Slice(_indices, 0, _indices.size()), _bbox, 0); + this; + } + + method: PointTree (PointTree; Vec[] points) + { + _root = nil; + _bbox = nil; + _indices = nil; + _points = nil; + _compFuncs = PointTree.CompareFunc[3](); + this.build(points); + } + + function: split (void; + PointTree this, + Node node, + Slice slice, + BBox bbox, + int depth) + { + if (slice.size() <= 8) + { + node._indices = slice; + } + else + { + let dimension = bbox.majorAxis(); + node._dimension = dimension; + + // + // Why can't I make one big let statement instead of 7 + // separate ones? + // + + let bsplit = bbox.center()[dimension], + s = slice.size(), + leftSize = s / 2, + rightSize = s - leftSize, + rslice = Slice(slice, leftSize, s), + lslice = Slice(slice, 0, leftSize), + nth = nth_element(slice, leftSize, _compFuncs[dimension]); + + node._splitValue = _points[nth][dimension]; + + if (leftSize > 0) + { + BBox newBox = BBox(bbox); + newBox.max[dimension] = bsplit; + node._left = Node(); + split(this, node._left, lslice, newBox, depth+1); + } + + if (rightSize > 0) + { + BBox newBox = BBox(bbox); + newBox.min[dimension] = bsplit; + node._right = Node(); + split(this, node._right, rslice, newBox, depth+1); + } + } + } + + function: _intersect (IndexArray; PointTree this, + Node n, IndexArray a, Vec p, Scalar r) + { + if (n.isLeaf()) + { + for (int i=0, s=n._indices.size(); i < s; i++) + { + int index = n._indices[i]; + float m = mag(_points[index] - p); + if (m <= r) a.push_back(index); + } + } + else + { + if ((p[n._dimension] - r) < n._splitValue && n.hasLeft()) + { + _intersect(this, n._left, a, p, r); + } + + if ((p[n._dimension] + r) >= n._splitValue && n.hasRight()) + { + _intersect(this, n._right, a, p, r); + } + } + + a; + } + + function: intersect (IndexArray; PointTree this, Vec point, Scalar radius) + { + _intersect(this, _root, IndexArray(), point, radius); + } +} + + +//---------------------------------------------------------------------- + +function: test_nth_element (void;) +{ + int[] a = {12, 17, 2, 19, 4, 6, 10, 15, 7, 18, 8, 20, 0, + 3, 16, 11, 5, 9, 1, 14, 13}; + + print("a is " + string(a) + "\n"); + + for (int i=0; i < a.size(); i++) + { + int[] acopy = int[](a); + let nth = nth_element(acopy, i, \: (int;int a, int b) {a - b;}); + print("nth " + i + " is " + nth + " " + string(acopy) + "\n"); + } +} + +function: slice_test (void;) +{ + int[] a = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + let s = Slice(Slice(a, 2, 8), 2, 4); + for (int i=0; i < s.size(); i++) print("s["+i+"] = " + s[i] + "\n"); +} + +function: bbox_test (void;) +{ + BBox bbox; + bbox.makeEmpty(); + bbox.extend(Vec(1,0,0)); + bbox.extend(Vec(0,1,0)); + bbox.extend(Vec(0,0,1)); + bbox.extend(Vec(-1,0,0)); + bbox.extend(Vec(0,-1,0)); + bbox.extend(Vec(0,0,-1)); + print(string(bbox) + "\n"); +} + +//test_nth_element(); +//slice_test(); +//bbox_test(); +}; + diff --git a/src/bin/mu/mu-interp/test/ptree_test.mu b/src/bin/mu/mu-interp/test/ptree_test.mu new file mode 100644 index 000000000..4ad242f09 --- /dev/null +++ b/src/bin/mu/mu-interp/test/ptree_test.mu @@ -0,0 +1,4043 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +use point_tree; +use math_util; +require runtime; + +print("module locations: %s\n" % runtime.module_locations()); + +//use runtime; + +//\: gcc (void;) +//{ + //print("gc: " + + //"collected " + gc_collected_objects() + + //", scanned " + gc_scanned_objects() + "\n"); +//} + +//runtime.set_collect_threshold(6500); +//runtime.set_aux_collect_threshold(1024 * 11); +//runtime.call_on_collect(gcc); + +math.vec3f[] points = { +{0.773228, -0.0707359, 0.63017}, +{0.416156, 0.745204, -0.521042}, +{-0.576685, 0.51096, -0.63746}, +{0.628077, -0.0545077, 0.77624}, +{-0.207712, 0.819163, -0.534629}, +{-0.712806, 0.565174, 0.415313}, +{-0.458428, 0.468577, -0.755168}, +{0.889964, -0.316396, -0.328418}, +{-0.627641, 0.455314, -0.631472}, +{-0.584438, 0.547328, 0.599053}, +{0.712047, -0.700045, 0.0540959}, +{-0.011151, 0.963789, 0.266432}, +{-0.856277, 0.347952, -0.381732}, +{-0.144926, 0.867855, -0.475209}, +{0.00939158, -0.988231, -0.152679}, +{-0.136484, -0.859115, -0.493248}, +{-0.91387, 0.405732, -0.0149339}, +{0.755954, -0.564009, 0.332306}, +{0.134026, -0.96587, 0.22166}, +{-0.771265, -0.485879, 0.411185}, +{0.0295565, 0.995518, 0.0898323}, +{0.393383, -0.835427, 0.383812}, +{0.796791, -0.601556, 0.0570406}, +{0.906888, -0.305162, -0.290568}, +{0.617255, 0.647751, 0.44656}, +{0.145027, 0.230601, 0.96218}, +{0.403916, -0.445564, -0.798953}, +{-0.325151, 0.921561, 0.212139}, +{-0.739689, -0.487003, 0.464422}, +{0.951377, -0.289948, -0.10398}, +{-0.42954, 0.327362, -0.841623}, +{0.309021, -0.368969, 0.876566}, +{0.410972, 0.0270727, 0.911246}, +{-0.45521, 0.76524, 0.455184}, +{0.468237, 0.249501, 0.847646}, +{-0.112315, 0.609811, 0.784548}, +{0.936858, 0.145701, -0.317912}, +{-0.132263, 0.517646, -0.84531}, +{0.938413, 0.306321, -0.159841}, +{0.735148, -0.605443, 0.304952}, +{-0.277552, -0.247351, -0.928322}, +{-0.305883, -0.94938, -0.0715083}, +{0.110194, -0.439405, -0.891505}, +{-0.764829, -0.157707, 0.624632}, +{0.743616, 0.0244738, -0.668159}, +{-0.100227, 0.994424, 0.0327802}, +{0.738528, -0.523892, -0.424397}, +{-0.39648, -0.277122, -0.875218}, +{-0.358714, 0.618606, -0.699036}, +{0.595104, -0.801108, -0.0638533}, +{-0.56224, 0.0793175, -0.823162}, +{-0.400038, -0.544725, 0.737051}, +{-0.902739, -0.340511, 0.262897}, +{0.135766, 0.291534, -0.946877}, +{-0.298857, 0.871436, 0.388953}, +{0.72502, -0.549745, -0.41488}, +{0.741171, 0.576167, 0.344525}, +{0.299818, 0.706882, -0.640646}, +{-0.190345, 0.852489, -0.486859}, +{0.687994, -0.719283, 0.0964165}, +{-0.211431, -0.114313, -0.970685}, +{0.475486, -0.484484, 0.734294}, +{-0.945383, -0.196005, -0.260446}, +{-0.199893, -0.146118, 0.968861}, +{-0.116254, 0.522693, 0.844557}, +{0.328029, 0.934942, 0.135202}, +{-0.549693, 0.795764, 0.254161}, +{-0.691615, -0.537071, -0.482932}, +{-0.425136, 0.120736, 0.897041}, +{0.720801, -0.11663, 0.68326}, +{-0.760693, -0.537024, -0.364625}, +{-0.490681, -0.521062, -0.698374}, +{-0.635408, -0.200028, 0.745819}, +{-0.462631, -0.215397, -0.859986}, +{0.088217, 0.522754, -0.847907}, +{-0.941341, 0.268944, -0.203829}, +{-0.696294, 0.652568, 0.298881}, +{-0.263004, 0.76037, -0.593857}, +{-0.0372477, -0.116435, 0.9925}, +{-0.262251, 0.483955, -0.834872}, +{0.559407, -0.735327, -0.382567}, +{-0.91208, -0.106689, 0.395889}, +{0.269606, 0.811593, 0.518295}, +{-0.85237, -0.298187, 0.429594}, +{0.646107, 0.747663, -0.153444}, +{0.374832, 0.646946, 0.66405}, +{-0.397651, 0.566523, 0.721751}, +{0.447445, 0.0366508, -0.89356}, +{-0.0851264, -0.587765, -0.804541}, +{-0.872947, 0.220017, -0.43538}, +{-0.198704, -0.344756, 0.91742}, +{-0.0705505, 0.206695, 0.975858}, +{-0.278725, -0.18281, -0.942811}, +{0.853215, 0.361167, 0.376274}, +{0.723529, -0.158771, 0.671787}, +{-0.0692671, -0.0403614, -0.996781}, +{-0.369508, -0.896766, -0.243463}, +{0.460927, 0.875264, 0.146489}, +{0.898307, -0.43282, -0.0755708}, +{-0.752539, -0.0288221, 0.657917}, +{-0.963828, 0.162074, 0.211584}, +{-0.895963, 0.204815, -0.394083}, +{-0.751503, -0.603236, -0.267112}, +{-0.292005, -0.881827, -0.370289}, +{-0.731042, 0.584741, -0.351648}, +{-0.569985, -0.606159, -0.554696}, +{0.984065, 0.135082, 0.115625}, +{-0.409119, 0.145142, -0.900864}, +{0.0468043, -0.117721, -0.991943}, +{0.835866, 0.548802, 0.0119943}, +{-0.296009, -0.847248, 0.441078}, +{0.456364, 0.647987, 0.609791}, +{-0.879321, -0.381379, 0.285211}, +{0.438641, 0.665255, 0.604177}, +{0.233721, -0.350261, 0.907024}, +{0.740642, -0.670459, 0.0439889}, +{-0.0568553, 0.821598, 0.567226}, +{0.728787, -0.464168, -0.503405}, +{-0.697234, -0.656311, -0.288307}, +{-0.562232, 0.790107, -0.244185}, +{-0.398445, -0.838486, -0.37173}, +{0.433112, 0.846443, -0.309755}, +{0.0608475, -0.860467, 0.50586}, +{-0.610856, 0.786375, -0.0920245}, +{-0.779644, 0.525653, -0.340358}, +{0.812107, 0.0300884, 0.582732}, +{-0.182346, 0.965103, 0.187952}, +{0.911246, 0.33048, 0.245793}, +{0.267164, -0.388335, -0.881941}, +{0.187956, 0.884571, 0.426857}, +{-0.314674, 0.931449, -0.182711}, +{-0.307942, -0.588211, 0.747783}, +{-0.107161, 0.580741, -0.807004}, +{0.232607, 0.106493, 0.966723}, +{-0.547762, -0.625059, 0.55611}, +{-0.691053, -0.438266, 0.574776}, +{-0.813995, -0.578978, 0.0468682}, +{-0.112682, -0.644158, -0.756547}, +{0.0528935, -0.841249, -0.538054}, +{0.375052, -0.682218, 0.627626}, +{-0.989778, -0.00415305, -0.14256}, +{-0.330601, -0.715029, 0.615984}, +{0.81196, 0.157358, 0.562103}, +{0.223804, 0.932213, -0.284415}, +{0.759406, -0.64658, -0.0723655}, +{0.456811, -0.0109105, 0.889497}, +{-0.564292, -0.641908, -0.519161}, +{-0.544406, -0.300056, 0.783319}, +{0.0811832, -0.0327508, 0.996161}, +{0.279577, 0.378714, -0.882277}, +{0.250052, 0.630417, 0.73488}, +{-0.700104, 0.633747, -0.328969}, +{0.0465434, 0.862122, -0.504558}, +{-0.479891, 0.856301, 0.190924}, +{-0.960344, 0.00736862, 0.278719}, +{0.687248, 0.469702, 0.554138}, +{-0.307253, 0.0928629, 0.947086}, +{-0.877638, -0.223389, -0.424086}, +{0.268897, 0.626969, -0.731166}, +{0.0768966, 0.607158, -0.790852}, +{0.844343, -0.470578, -0.256205}, +{-0.821025, -0.530532, 0.210841}, +{0.118125, -0.43139, 0.894399}, +{0.514356, -0.725651, 0.457021}, +{0.356702, -0.49472, 0.792474}, +{-0.943802, 0.328363, 0.0376221}, +{0.523588, -0.706405, -0.476285}, +{0.22104, 0.96149, 0.163336}, +{-0.636745, -0.129741, -0.760081}, +{0.476682, 0.308092, 0.823319}, +{0.807517, 0.499063, 0.314409}, +{0.531268, 0.20802, 0.821269}, +{-0.856158, -0.330728, -0.397004}, +{0.969985, -0.115444, 0.214012}, +{0.355389, 0.054517, 0.933127}, +{-0.84136, 0.369781, -0.394176}, +{0.628313, -0.699033, 0.341431}, +{0.0869167, -0.544611, 0.834173}, +{0.715635, 0.204073, -0.667997}, +{0.549517, -0.456195, -0.699941}, +{-0.0865944, 0.180061, -0.979836}, +{0.995059, 0.0984135, -0.0131148}, +{-0.873228, -0.459479, 0.162334}, +{-0.463993, 0.0894609, -0.88131}, +{0.766488, 0.482736, 0.42363}, +{0.392496, 0.907114, -0.151958}, +{-0.298302, -0.637138, 0.710683}, +{0.337122, -0.191893, -0.921697}, +{-0.301024, 0.278723, 0.911975}, +{-0.952967, -0.272177, 0.133316}, +{-0.222683, -0.812622, -0.538571}, +{0.327837, 0.540293, 0.774988}, +{0.979412, 0.0957793, 0.177705}, +{0.698319, -0.577057, -0.423504}, +{0.165388, 0.563774, -0.8092}, +{0.353977, -0.923373, 0.148602}, +{-0.398198, -0.344001, 0.850354}, +{0.848922, 0.522413, -0.0800965}, +{-0.406472, -0.893471, 0.191024}, +{0.906404, 0.414709, -0.0803057}, +{-0.845286, 0.0774964, 0.528663}, +{0.0840234, 0.537443, 0.839104}, +{0.239902, -0.8678, -0.435166}, +{-0.236993, -0.697165, 0.676606}, +{-0.293545, -0.369797, 0.881522}, +{0.630149, 0.383197, 0.675331}, +{0.510814, -0.837994, 0.191927}, +{-0.378447, 0.925, -0.0339665}, +{-0.786209, 0.278189, -0.551803}, +{0.145857, -0.781952, -0.606034}, +{0.857178, -0.163386, 0.488417}, +{-0.661816, 0.198877, 0.722805}, +{-0.54541, -0.724986, -0.420621}, +{0.894865, -0.227065, 0.384264}, +{0.424952, -0.90038, -0.0934404}, +{-0.379815, -0.601471, 0.702833}, +{0.908643, -0.38995, -0.149354}, +{-0.204291, 0.411466, -0.888235}, +{0.0818337, -0.983081, -0.163877}, +{-0.856062, -0.506671, -0.102187}, +{0.996917, -0.0635778, 0.0459764}, +{0.308308, -0.587913, 0.747867}, +{0.497123, -0.808183, 0.315767}, +{0.669899, 0.601062, 0.435844}, +{-0.940703, -0.00564236, 0.339185}, +{-0.242015, 0.151756, 0.958331}, +{0.506813, 0.815821, -0.278526}, +{0.444599, 0.760716, -0.47291}, +{0.310941, -0.935585, -0.167323}, +{-0.840326, 0.537417, 0.0709556}, +{0.291353, -0.00415028, 0.956607}, +{0.283437, -0.827602, -0.484499}, +{-0.775543, -0.624146, -0.0947355}, +{0.690953, -0.670945, -0.269102}, +{0.643273, -0.762381, 0.0705382}, +{0.0395429, -0.928014, 0.37044}, +{-0.996862, 0.0167314, -0.0773699}, +{0.163089, -0.939767, 0.300399}, +{-0.256541, -0.201666, -0.945261}, +{-0.492165, 0.622103, -0.608902}, +{0.23486, -0.561388, -0.793526}, +{-0.89888, -0.415453, -0.139334}, +{-0.0952387, 0.0558736, 0.993885}, +{0.468325, -0.75266, -0.46279}, +{0.420054, -0.233894, -0.87684}, +{0.889733, 0.387452, -0.241363}, +{-0.575333, -0.766234, 0.286142}, +{-0.868292, -0.426643, 0.253072}, +{-0.646458, 0.686084, -0.333737}, +{0.00856148, 0.303768, -0.952708}, +{-0.723183, -0.690376, -0.0196847}, +{-0.116966, 0.912956, 0.390936}, +{-0.627502, -0.0296827, 0.778049}, +{0.160989, -0.925888, -0.341781}, +{0.399747, 0.535066, 0.744249}, +{-0.546954, 0.835973, -0.0446094}, +{0.846422, 0.250391, -0.469973}, +{-0.656874, 0.741554, -0.136433}, +{-0.519668, 0.302656, 0.798965}, +{0.983444, 0.132693, -0.123412}, +{-0.106091, -0.991249, -0.0785494}, +{-0.301078, -0.852039, 0.428232}, +{0.86812, -0.112627, -0.483407}, +{-0.351069, -0.289318, 0.890531}, +{0.675497, -0.144483, 0.723069}, +{0.867892, -0.471571, 0.156153}, +{-0.681761, 0.0779361, 0.727412}, +{-0.742264, 0.454137, -0.492751}, +{0.775695, 0.592746, -0.216679}, +{-0.109107, 0.174172, 0.978652}, +{0.947582, 0.170928, -0.269949}, +{-0.185424, 0.636468, -0.748683}, +{0.366333, 0.46219, -0.807577}, +{-0.108753, 0.844424, 0.52452}, +{0.531399, -0.843963, -0.0730813}, +{0.127826, -0.991389, -0.0284202}, +{-0.791628, -0.542226, 0.281629}, +{0.217244, 0.84079, -0.49586}, +{-0.11878, 0.991226, -0.0579812}, +{-0.802878, -0.507145, -0.313354}, +{-0.500109, -0.843081, 0.197749}, +{0.158633, -0.222318, 0.961982}, +{0.0220684, 0.253949, 0.966966}, +{-0.314337, 0.686753, 0.65541}, +{-0.302056, -0.340231, -0.890508}, +{0.891865, -0.443889, 0.0868347}, +{-0.466258, 0.318577, -0.825295}, +{-0.530974, -0.846639, -0.0356056}, +{-0.486421, 0.362926, 0.794783}, +{0.919445, 0.33796, -0.201009}, +{0.775145, 0.392438, -0.495118}, +{0.300117, -0.460474, -0.8354}, +{-0.993074, 0.114075, -0.0281272}, +{0.258174, 0.928974, -0.26524}, +{0.907233, -0.41835, -0.0437305}, +{-0.738859, 0.238482, -0.63025}, +{-0.918257, 0.314584, -0.240502}, +{0.588211, -0.136498, -0.797105}, +{0.186011, 0.643534, 0.742471}, +{0.121775, -0.0281642, -0.992158}, +{0.526695, -0.181093, -0.830541}, +{-0.950147, -0.183778, 0.251884}, +{0.926374, -0.286535, 0.244396}, +{-0.783032, -0.614788, 0.0943231}, +{0.468353, -0.835417, 0.287617}, +{0.637894, 0.0836099, -0.765572}, +{-0.987965, 0.0797934, 0.132505}, +{-0.759042, -0.0957109, -0.643968}, +{-0.292905, 0.764565, 0.574149}, +{0.437598, 0.133189, 0.889252}, +{-0.803098, -0.111377, -0.585344}, +{-0.461463, 0.770282, -0.440134}, +{0.668501, -0.597496, 0.442837}, +{0.228321, 0.146993, -0.962425}, +{0.919585, -0.391379, 0.0344453}, +{-0.311667, -0.927296, -0.207331}, +{-0.644564, -0.589707, -0.486604}, +{-0.201312, -0.96964, -0.13882}, +{0.0818045, 0.767619, 0.635665}, +{-0.85612, 0.00495211, -0.516754}, +{-0.340929, -0.84392, -0.414206}, +{0.637635, -0.428655, -0.64006}, +{0.776018, 0.0691716, 0.626907}, +{0.139244, 0.0653424, -0.9881}, +{-0.828921, 0.505302, 0.239915}, +{0.095148, -0.604472, 0.790924}, +{0.475247, -0.800319, -0.365555}, +{-0.306444, 0.947083, 0.0955311}, +{-0.0115119, 0.0940581, 0.9955}, +{-0.0351699, 0.220168, -0.974828}, +{-0.569081, 0.69178, 0.444507}, +{0.613257, -0.719014, 0.327008}, +{-0.840663, -0.0987814, -0.532473}, +{0.714746, -0.198586, 0.670597}, +{0.185513, 0.214041, -0.959047}, +{0.646273, -0.754485, -0.114387}, +{0.0872603, -0.950902, -0.296936}, +{0.618238, -0.579992, -0.530463}, +{-0.226296, 0.880014, 0.417571}, +{0.604464, -0.0269044, 0.796178}, +{-0.0111121, -0.210355, -0.977562}, +{0.362683, -0.808882, -0.462785}, +{0.908004, -0.370596, -0.195413}, +{-0.455336, -0.872387, -0.177791}, +{0.421336, 0.615252, -0.666289}, +{0.700132, -0.522946, 0.486152}, +{-0.739493, -0.590829, 0.322601}, +{-0.711318, -0.187868, -0.677298}, +{-0.374682, 0.847002, 0.377095}, +{0.341554, 0.568445, -0.748473}, +{-0.490356, 0.737937, 0.463681}, +{0.490427, 0.451831, 0.745205}, +{0.577985, -0.273918, -0.768702}, +{0.48346, -0.3793, -0.788922}, +{0.638055, 0.474414, 0.606479}, +{-0.216246, 0.913922, -0.343488}, +{-0.612006, -0.7625, -0.209865}, +{-0.0462658, -0.888707, 0.456136}, +{0.531916, -0.517578, -0.670207}, +{-0.124269, -0.181183, 0.975566}, +{0.968812, 0.247608, 0.0096367}, +{-0.466169, 0.212727, 0.85874}, +{-0.399143, -0.776152, 0.488132}, +{0.449739, -0.726393, 0.5197}, +{0.372397, 0.650509, -0.661935}, +{0.247224, 0.28173, -0.927097}, +{-0.0269669, -0.425197, -0.904699}, +{-0.592281, -0.624566, -0.509039}, +{0.223519, 0.969609, 0.099483}, +{-0.685228, -0.312143, -0.65805}, +{-0.979773, -0.0630721, -0.189912}, +{-0.312331, 0.50967, -0.801677}, +{-0.772604, -0.42402, -0.472535}, +{-0.0865618, -0.628242, 0.773187}, +{0.628366, -0.547202, -0.552925}, +{-0.95258, -0.304165, -0.00862861}, +{0.185436, -0.980757, -0.0610624}, +{0.256248, -0.781323, -0.569097}, +{0.672993, 0.716756, -0.182595}, +{-0.263249, -0.878413, 0.398862}, +{0.236007, 0.384086, -0.892625}, +{-0.354199, 0.86761, -0.348993}, +{-0.968795, -0.155169, 0.193283}, +{0.607872, -0.757559, -0.237897}, +{-0.00713383, 0.991308, 0.131371}, +{0.701572, -0.200974, -0.683672}, +{0.196454, -0.123137, -0.97275}, +{-0.362283, 0.920234, 0.148054}, +{0.980146, 0.140626, 0.139781}, +{-0.236169, -0.26534, -0.934783}, +{0.724327, 0.663034, 0.189039}, +{0.103977, 0.235629, -0.966265}, +{0.438479, -0.549893, -0.710882}, +{0.294696, 0.585785, -0.75499}, +{-0.222673, 0.545372, -0.808075}, +{-0.0329392, 0.697707, 0.715626}, +{0.823651, -0.441359, 0.356092}, +{0.878058, 0.340673, -0.336089}, +{0.737195, -0.414679, -0.533466}, +{0.343596, 0.403489, 0.84802}, +{0.301732, -0.953392, -0.00134519}, +{-0.579529, 0.0248752, 0.814572}, +{-0.311992, 0.203486, -0.928038}, +{-0.260622, -0.929731, -0.260147}, +{-0.488032, -0.872512, 0.0234217}, +{0.670019, -0.674115, 0.310876}, +{-0.492451, -0.724536, 0.482224}, +{0.272679, 0.442394, -0.854362}, +{0.50102, -0.523078, -0.689469}, +{-0.161663, -0.783272, -0.600291}, +{0.463239, 0.230664, -0.855689}, +{0.940174, 0.201025, 0.275067}, +{0.350531, -0.917765, 0.186642}, +{-0.0998532, 0.577415, 0.810322}, +{-0.0863993, 0.186613, 0.978627}, +{-0.166405, 0.294152, -0.941161}, +{-0.583173, -0.549524, -0.598275}, +{0.398755, 0.149651, 0.904764}, +{-0.740899, -0.667812, 0.0713804}, +{0.789223, 0.291558, 0.540482}, +{0.556241, 0.333462, -0.761183}, +{-0.157284, -0.182877, -0.970473}, +{0.856007, 0.292164, -0.426488}, +{0.861275, 0.294925, 0.413793}, +{-0.130793, 0.985458, 0.10847}, +{-0.762373, -0.545066, 0.348841}, +{0.84, 0.396466, 0.370424}, +{-0.266077, 0.363858, 0.892642}, +{-0.167493, 0.645786, 0.74492}, +{-0.605832, 0.766507, 0.213155}, +{0.414851, 0.831477, 0.36952}, +{0.21232, 0.964617, -0.156312}, +{-0.46792, 0.875638, 0.119621}, +{-0.751071, 0.178672, 0.635585}, +{-0.650807, -0.739142, -0.173549}, +{-0.0354957, -0.992348, 0.118261}, +{0.476122, 0.561257, -0.676977}, +{-0.106678, -0.992948, 0.0517096}, +{-0.284602, 0.868744, -0.405322}, +{0.1357, -0.564783, -0.814006}, +{-0.788524, 0.476375, -0.388968}, +{-0.289847, 0.0999372, -0.951841}, +{0.958147, 0.0428704, 0.283047}, +{0.367085, 0.249387, -0.896133}, +{0.685667, -0.727446, -0.0261389}, +{-0.842658, 0.123725, -0.524042}, +{-0.952321, -0.0492754, 0.301092}, +{-0.0745599, 0.502934, -0.861103}, +{0.61357, 0.715498, -0.334058}, +{0.0374117, -0.0693219, 0.996893}, +{0.971757, -0.0646142, -0.226964}, +{0.129452, -0.885515, -0.446212}, +{-0.412986, -0.283162, -0.865599}, +{0.950625, 0.219012, -0.219877}, +{0.000377241, -0.37777, 0.9259}, +{-0.0583186, 0.698077, -0.713644}, +{0.987127, -0.0812919, 0.137738}, +{-0.170983, -0.915225, 0.364867}, +{-0.874839, 0.446731, 0.187317}, +{-0.22926, 0.0842784, 0.96971}, +{-0.633999, 0.592477, -0.497007}, +{0.221638, 0.337431, 0.914886}, +{-0.560613, -0.111787, 0.820498}, +{-0.445934, 0.479402, -0.755855}, +{-0.993226, -0.0930046, 0.0696522}, +{0.194687, 0.512055, 0.836598}, +{-0.184829, 0.639398, 0.74633}, +{-0.802382, 0.382521, -0.458106}, +{-0.522831, 0.678685, 0.515786}, +{-0.792363, -0.361871, 0.491132}, +{-0.196644, 0.360822, 0.911668}, +{0.94788, 0.234396, -0.215829}, +{0.229017, 0.929739, 0.288335}, +{0.884601, -0.339868, 0.319328}, +{-0.908981, 0.0302261, 0.415741}, +{-0.402534, 0.810525, 0.425458}, +{-0.509417, 0.859249, 0.0467497}, +{-0.412527, -0.362976, -0.835506}, +{0.699643, -0.68625, 0.1989}, +{-0.124346, -0.871782, -0.473851}, +{0.260759, -0.96334, 0.0630921}, +{-0.189283, 0.167385, -0.967551}, +{0.818593, 0.458835, -0.345508}, +{-0.689358, 0.119099, -0.714563}, +{-0.618479, 0.380763, 0.687389}, +{-0.542576, 0.67917, 0.494308}, +{0.102588, 0.989229, 0.10441}, +{-0.55921, -0.735884, 0.381784}, +{-0.474345, -0.872634, 0.116215}, +{0.976822, -0.201082, -0.0733825}, +{0.863344, 0.259734, 0.432638}, +{-0.221982, -0.107931, -0.969059}, +{-0.0926029, 0.754243, -0.650033}, +{-0.0013734, -0.465455, -0.885071}, +{0.880387, 0.472527, -0.0404635}, +{-0.103507, 0.54038, 0.83503}, +{-0.522344, -0.741191, 0.421655}, +{-0.250401, -0.481238, -0.840065}, +{0.874307, -0.42232, -0.239235}, +{-0.365752, -0.90852, -0.202031}, +{0.164677, 0.797031, 0.581054}, +{0.857968, 0.468565, 0.210567}, +{0.447747, 0.280304, -0.849089}, +{0.411891, 0.466384, -0.782835}, +{-0.0955243, 0.910918, 0.401376}, +{0.112742, 0.974895, -0.192011}, +{-0.0619714, 0.986578, 0.151077}, +{0.110341, 0.876868, -0.467897}, +{0.917522, 0.252298, -0.307406}, +{-0.0363539, 0.964961, -0.259863}, +{-0.117382, 0.951355, -0.284858}, +{0.696467, -0.358928, 0.621373}, +{-0.130061, -0.932408, -0.337192}, +{-0.758023, 0.531265, -0.378363}, +{0.684916, -0.435688, -0.584009}, +{0.589049, -0.568839, 0.573972}, +{-0.0994089, 0.45859, -0.88307}, +{-0.306201, -0.777194, -0.549736}, +{0.933831, 0.356059, 0.0343737}, +{0.99504, 0.0651517, 0.075175}, +{0.889513, -0.208926, 0.406345}, +{0.420731, 0.444207, 0.79099}, +{0.0738016, 0.971211, -0.226499}, +{0.589156, -0.412732, 0.694657}, +{-0.889502, 0.348919, 0.295027}, +{-0.0244612, 0.525746, 0.85029}, +{-0.276309, 0.812251, 0.513713}, +{0.520828, 0.532287, -0.66739}, +{-0.440628, 0.227211, -0.86846}, +{0.72494, 0.161926, -0.669509}, +{-0.889597, 0.355062, -0.287313}, +{0.637384, -0.474001, -0.607507}, +{0.929749, -0.0573182, -0.363704}, +{0.315388, 0.849149, -0.423647}, +{-0.00928232, 0.775113, -0.631754}, +{0.351872, 0.893108, -0.280259}, +{0.382798, 0.741013, 0.551693}, +{-0.867357, 0.233499, 0.439511}, +{0.16179, -0.872313, -0.461404}, +{0.371433, 0.694844, 0.615816}, +{0.275648, 0.0562907, 0.959609}, +{-0.30377, 0.58926, -0.748663}, +{0.107535, 0.0326808, -0.993664}, +{0.928925, -0.365076, 0.0617895}, +{-0.906819, 0.182671, 0.379883}, +{-0.816987, 0.476979, -0.324072}, +{-0.948634, -0.228325, 0.218999}, +{-0.306723, 0.747404, 0.589329}, +{0.523748, -0.601268, -0.603461}, +{-0.367995, -0.0464385, 0.928667}, +{-0.385434, -0.108926, -0.916284}, +{0.626324, 0.301384, -0.718948}, +{-0.929887, -0.292036, 0.223663}, +{-0.911464, 0.0343044, -0.409947}, +{0.655025, 0.750593, 0.0869031}, +{0.834327, 0.477193, -0.276017}, +{-0.73715, 0.67071, -0.082209}, +{0.406418, 0.207343, 0.88985}, +{-0.384989, -0.266046, -0.883744}, +{0.826796, 0.556442, -0.0823459}, +{0.27884, -0.526639, -0.803056}, +{0.345377, 0.934063, 0.0907744}, +{0.630473, 0.574372, 0.522112}, +{0.0634988, -0.426974, -0.902032}, +{0.165134, 0.137897, 0.976583}, +{0.507099, -0.0612336, 0.85971}, +{-0.188623, -0.954086, -0.232684}, +{-0.74464, 0.66742, 0.00787858}, +{0.948294, 0.296856, 0.112318}, +{0.907994, 0.0747335, 0.412264}, +{0.056506, -0.895198, -0.442073}, +{-0.767455, 0.588742, 0.253763}, +{-0.121601, -0.905084, -0.407475}, +{-0.644886, 0.138852, 0.75156}, +{-0.172007, 0.488813, 0.855263}, +{-0.165697, 0.98617, -0.00366394}, +{0.301881, -0.72227, 0.62225}, +{0.681361, 0.0795133, 0.727616}, +{-0.28768, -0.470468, 0.834206}, +{0.30044, 0.347393, 0.888287}, +{0.646263, 0.495182, -0.580636}, +{0.663723, 0.670265, 0.331991}, +{0.93092, -0.225786, 0.28707}, +{-0.70559, -0.191705, -0.682196}, +{-0.513263, -0.174103, -0.840386}, +{0.232144, -0.631352, 0.739935}, +{0.302295, 0.920832, 0.246348}, +{-0.748212, 0.519721, 0.412393}, +{-0.188031, 0.536649, -0.822589}, +{0.658181, -0.166369, -0.734247}, +{0.0335146, 0.96001, 0.277954}, +{-0.105593, -0.579084, -0.808401}, +{0.0975521, 0.832126, -0.54594}, +{0.0831698, 0.993836, -0.0733037}, +{0.170407, 0.630268, 0.757446}, +{-0.578746, -0.497588, 0.64611}, +{-0.102065, -0.404845, -0.908671}, +{0.0637224, 0.13331, 0.989024}, +{0.504746, 0.84157, 0.192334}, +{-0.785196, -0.0177103, 0.618994}, +{-0.0443331, 0.998739, -0.0235682}, +{0.0233999, 0.626399, -0.779152}, +{0.46265, -0.550107, 0.695225}, +{-0.466103, 0.873081, -0.143101}, +{-0.298378, 0.760344, -0.576929}, +{0.239922, 0.908985, 0.340856}, +{-0.370136, -0.318432, 0.872697}, +{0.991622, -0.123139, 0.0390237}, +{0.313605, 0.261912, 0.912718}, +{-0.658128, -0.266672, 0.704098}, +{0.435646, 0.396013, -0.808323}, +{0.812477, -0.294306, -0.503255}, +{-0.886333, 0.141658, -0.440848}, +{0.178769, -0.971434, -0.156066}, +{-0.243618, 0.212382, -0.946332}, +{0.327126, -0.582619, -0.744005}, +{-0.173727, 0.764477, 0.620801}, +{0.781104, 0.123967, 0.611971}, +{-0.657394, 0.475693, -0.584422}, +{-0.290956, 0.945802, 0.144232}, +{-0.772895, 0.270237, 0.574112}, +{-0.829433, 0.16689, 0.533093}, +{-0.48439, -0.658587, 0.575873}, +{-0.996646, 0.0631679, 0.0520179}, +{-0.0419412, 0.980592, 0.191522}, +{-0.256055, -0.913044, -0.317469}, +{0.449281, -0.447753, 0.773087}, +{0.879859, 0.466911, -0.0885525}, +{0.318438, 0.947914, -0.00753829}, +{0.695515, -0.558873, -0.451575}, +{0.250314, -0.963127, -0.0986358}, +{-0.186475, 0.17756, 0.966281}, +{0.521197, -0.825719, -0.215736}, +{0.123416, 0.909098, 0.397882}, +{0.982546, 0.18552, 0.0136277}, +{-0.364027, -0.00960808, -0.931339}, +{0.178895, 0.366162, -0.913193}, +{-0.881963, -0.0760474, 0.465143}, +{-0.498786, 0.394994, 0.771487}, +{0.390364, -0.694142, 0.6048}, +{0.95186, -0.237071, -0.194319}, +{0.446884, 0.886582, 0.119445}, +{-0.94342, 0.326027, -0.0605372}, +{0.439269, -0.831524, 0.340015}, +{0.0989651, -0.190992, -0.97659}, +{0.275829, -0.140987, 0.950811}, +{-0.65735, -0.530387, 0.535332}, +{-0.176912, -0.339826, 0.923699}, +{0.76408, 0.269737, 0.586023}, +{-0.821621, 0.0958077, -0.561926}, +{-0.903307, -0.427459, 0.0362622}, +{0.868501, 0.0484461, -0.493314}, +{0.338876, 0.154679, 0.928029}, +{-0.461641, -0.432368, -0.774562}, +{0.610247, 0.364629, -0.70331}, +{0.271983, 0.345405, 0.898176}, +{-0.762101, 0.61205, 0.21118}, +{0.873855, 0.16304, -0.458034}, +{0.760497, -0.611354, -0.21884}, +{0.162009, 0.826212, 0.539561}, +{0.294726, -0.953332, -0.0655345}, +{-0.113676, -0.207936, 0.971514}, +{0.13588, 0.70142, 0.699676}, +{-0.68623, 0.359019, -0.632609}, +{-0.101776, -0.567788, 0.816859}, +{-0.417525, 0.906281, 0.0657799}, +{-0.89466, 0.188982, -0.404807}, +{-0.0161445, -0.333349, 0.942665}, +{-0.355301, -0.0211159, 0.934513}, +{0.299065, -0.909342, -0.289236}, +{-0.581135, -0.764344, -0.279393}, +{0.808778, -0.461172, -0.364963}, +{-0.623037, 0.758066, 0.192772}, +{-0.231193, -0.399469, 0.887116}, +{0.989199, 0.0886143, -0.116757}, +{-0.928657, 0.364705, -0.0677241}, +{0.65954, -0.209063, 0.722011}, +{-0.562258, -0.826759, -0.0183272}, +{0.794261, -0.370864, -0.481258}, +{-0.767887, 0.0576649, -0.637984}, +{-0.277037, 0.178042, 0.94422}, +{-0.975932, 0.104137, -0.191602}, +{0.407482, -0.490155, 0.770523}, +{-0.0060236, -0.277442, -0.960724}, +{0.59114, 0.786571, 0.178495}, +{-0.117313, 0.652223, -0.748895}, +{-0.541011, 0.144298, -0.828544}, +{-0.007412, -0.993, 0.11788}, +{0.372345, -0.526191, -0.764515}, +{0.679281, -0.713138, -0.173242}, +{0.228427, -0.971777, -0.0589166}, +{0.164661, 0.950704, -0.262769}, +{-0.217413, -0.862799, -0.45641}, +{-0.316876, 0.799573, -0.510169}, +{0.24787, -0.442011, -0.862082}, +{0.0327078, -0.221394, 0.974636}, +{-0.596142, 0.560674, -0.574682}, +{-0.313343, -0.70858, 0.632243}, +{-0.637126, -0.294744, -0.712177}, +{0.057382, -0.687515, -0.723899}, +{-0.73276, -0.67584, -0.0793936}, +{0.876699, -0.274034, 0.395353}, +{-0.922578, -0.129773, -0.36333}, +{-0.238693, 0.219974, 0.945853}, +{-0.815283, -0.0161969, -0.578836}, +{0.190955, 0.282583, 0.940044}, +{-0.953942, -0.215436, -0.208762}, +{0.788527, -0.45317, 0.415768}, +{0.545382, -0.739098, -0.395339}, +{0.553411, -0.779192, 0.294272}, +{-0.882816, -0.456382, -0.111136}, +{-0.92995, 0.349152, 0.115267}, +{-0.34724, -0.688126, -0.637109}, +{-0.0473178, -0.97814, -0.202493}, +{0.823553, -0.356671, 0.441073}, +{-0.0622451, -0.589876, 0.805091}, +{0.883198, 0.462455, 0.0780825}, +{-0.1316, 0.919219, -0.371102}, +{-0.950104, 0.255609, 0.17879}, +{-0.400712, -0.905228, 0.141394}, +{-0.675956, -0.0139011, -0.736811}, +{0.980282, -0.120089, 0.156926}, +{-0.624136, -0.172314, -0.762078}, +{0.00832381, 0.205654, -0.978589}, +{-0.664602, 0.739198, 0.109045}, +{-0.478563, -0.0841581, -0.874011}, +{-0.294034, 0.87857, 0.376376}, +{-0.51573, 0.794134, 0.321517}, +{-0.269378, 0.837387, -0.475625}, +{-0.446448, -0.757962, 0.475582}, +{-0.167328, -0.807857, 0.565127}, +{0.698802, -0.342471, -0.628004}, +{-0.519797, -0.776475, -0.356227}, +{0.110853, -0.138104, 0.984195}, +{0.636723, 0.137819, -0.758677}, +{-0.0459099, -0.398683, 0.915939}, +{-0.206355, -0.974313, -0.0901763}, +{0.948026, -0.137482, -0.286959}, +{0.208742, 0.131562, 0.969081}, +{-0.524804, -0.82482, 0.210361}, +{-0.287829, 0.644375, 0.708474}, +{0.211453, 0.977131, 0.0224358}, +{-0.973484, 0.228511, -0.0105757}, +{-0.0564095, 0.801855, 0.59485}, +{-0.128017, -0.764244, -0.632093}, +{-0.0114613, -0.807373, -0.58993}, +{0.513999, -0.328332, 0.792467}, +{-0.542349, 0.82885, 0.137349}, +{0.810354, 0.573412, 0.120518}, +{0.829778, 0.211053, -0.516647}, +{0.0495371, -0.392208, 0.918542}, +{-0.133149, 0.764289, -0.630978}, +{-0.593654, 0.464426, 0.657178}, +{-0.713579, 0.227889, -0.662474}, +{0.511538, -0.0171741, 0.859089}, +{-0.131576, 0.207652, -0.969313}, +{-0.238944, -0.769021, 0.592884}, +{-0.569536, -0.193796, -0.798794}, +{-0.0219794, 0.151472, -0.988217}, +{-0.712505, 0.0432761, -0.700331}, +{-0.302544, 0.426474, -0.852401}, +{0.844624, 0.429792, 0.3192}, +{-0.369507, 0.114876, 0.9221}, +{0.891473, -0.453073, 0.00105579}, +{-0.764008, 0.560936, 0.318816}, +{0.154022, -0.828932, 0.537726}, +{-0.858018, -0.504986, -0.0937793}, +{0.0318043, 0.510316, -0.859398}, +{0.284693, -0.937835, 0.198532}, +{-0.908086, -0.27243, -0.318058}, +{0.416079, 0.892522, -0.174018}, +{0.557645, 0.290139, 0.777722}, +{0.517932, -0.204962, -0.830504}, +{0.450044, -0.892486, -0.030502}, +{0.521959, -0.506421, 0.686365}, +{-0.0398415, -0.0492758, -0.99799}, +{-0.578023, 0.282477, -0.76557}, +{0.991716, -0.125238, -0.0285308}, +{0.997967, 0.05386, -0.0340607}, +{-0.500025, 0.830427, -0.245694}, +{-0.678151, 0.698175, 0.229486}, +{0.230158, 0.713623, -0.661641}, +{-0.253369, 0.729289, 0.635564}, +{0.416016, -0.9093, 0.0102363}, +{0.179937, -0.376593, -0.908736}, +{0.464963, 0.781267, -0.416452}, +{-0.223779, 0.584169, -0.780173}, +{-0.93953, 0.168826, 0.297962}, +{-0.794249, -0.569984, 0.210442}, +{0.044083, -0.49422, 0.868218}, +{0.427833, 0.546391, 0.720011}, +{-0.485778, -0.819769, -0.303312}, +{0.134052, -0.319738, -0.937975}, +{0.0610441, -0.910343, 0.409328}, +{-0.527754, 0.515239, 0.675281}, +{-0.262446, -0.956522, -0.127232}, +{0.741416, -0.254652, -0.620851}, +{-0.975139, -0.0212825, 0.22057}, +{-0.247109, 0.968391, -0.0339873}, +{0.657493, 0.616271, -0.433489}, +{-0.894538, 0.33301, 0.29817}, +{-0.700295, 0.588437, -0.40414}, +{-0.296899, 0.174579, 0.938815}, +{-0.647565, 0.619627, -0.443534}, +{0.956024, -0.206718, 0.208054}, +{-0.982597, -0.178311, 0.052037}, +{-0.59263, -0.684524, -0.424519}, +{0.849663, -0.414869, 0.32551}, +{0.245603, 0.968854, 0.0316425}, +{-0.718662, -0.670121, -0.18564}, +{0.235585, -0.624247, -0.744859}, +{-0.888594, -0.411102, 0.203458}, +{0.0573747, 0.390128, -0.918971}, +{0.897284, -0.0464668, 0.439001}, +{-0.424192, -0.400426, 0.812232}, +{0.390234, -0.0315722, 0.920174}, +{0.384768, 0.92236, -0.0347141}, +{0.534395, 0.181384, 0.825543}, +{0.272106, -0.224966, -0.935601}, +{0.772463, 0.188939, -0.606302}, +{-0.433775, -0.860076, -0.26853}, +{0.765406, 0.334075, 0.550043}, +{0.601338, 0.780836, 0.169376}, +{0.680769, 0.390947, -0.619447}, +{-0.240362, 0.109095, -0.964533}, +{0.837329, 0.306345, -0.452805}, +{-0.0516856, -0.823669, 0.56471}, +{0.479991, -0.482635, -0.732579}, +{0.00157147, -0.979391, 0.201969}, +{-0.86929, -0.350078, 0.348969}, +{-0.879117, 0.269414, -0.393154}, +{0.332893, -0.201929, 0.92109}, +{0.171121, -0.77776, 0.604819}, +{0.885766, 0.0188958, 0.463748}, +{0.524661, 0.588337, -0.615296}, +{-0.941107, 0.326086, 0.0893619}, +{0.169901, -0.122705, 0.977792}, +{-0.649489, 0.412738, -0.638601}, +{0.38914, 0.874108, 0.290697}, +{0.632782, -0.162629, 0.757059}, +{-0.980766, -0.13467, 0.141285}, +{-0.583803, -0.23593, 0.77686}, +{0.0387273, 0.94605, 0.321698}, +{-0.30302, -0.952445, 0.0320424}, +{0.451186, 0.891939, -0.0295882}, +{-0.423095, -0.724701, -0.543875}, +{0.665562, 0.435529, 0.606087}, +{0.798308, 0.598062, 0.0709049}, +{-0.852589, 0.495437, -0.166234}, +{-0.531388, 0.742556, 0.407723}, +{-0.532764, -0.84609, 0.0171641}, +{0.253853, -0.576223, -0.776869}, +{0.459048, -0.845309, -0.273364}, +{-0.474264, -0.0617456, 0.878215}, +{0.370301, -0.690091, -0.621813}, +{-0.755848, -0.269123, 0.596881}, +{0.0626481, -0.978606, -0.195971}, +{0.401462, 0.658984, 0.636057}, +{0.66764, -0.729175, 0.150204}, +{-0.897582, -0.439475, -0.0347703}, +{-0.519814, 0.235925, -0.821056}, +{-0.959402, 0.0691895, 0.273423}, +{-0.355833, 0.933777, 0.038001}, +{-0.164163, 0.986093, 0.0259026}, +{-0.0971891, -0.307116, 0.946697}, +{-0.625688, 0.0707773, 0.776856}, +{-0.951891, 0.278931, -0.12689}, +{0.343301, -0.727641, 0.593872}, +{0.757289, 0.548472, -0.354531}, +{0.94048, -0.179744, -0.288427}, +{0.82629, -0.492907, 0.272557}, +{-0.373644, 0.927313, -0.0219171}, +{-0.0148258, -0.774137, -0.632845}, +{0.474066, -0.843669, -0.251959}, +{0.386212, 0.922322, 0.0127142}, +{-0.0610782, 0.722745, 0.68841}, +{-0.876126, -0.00894416, 0.482}, +{0.378613, 0.916956, -0.125872}, +{-0.69009, 0.618916, 0.375125}, +{0.92193, 0.339139, -0.187163}, +{-0.083078, -0.231661, -0.969243}, +{-0.701255, -0.7125, 0.0241949}, +{-0.988043, 0.0487609, 0.146264}, +{0.255898, 0.514707, -0.818287}, +{-0.412533, -0.24059, -0.878597}, +{-0.518503, -0.52478, 0.6751}, +{-0.883045, 0.465878, 0.0564691}, +{0.491264, -0.665626, -0.561785}, +{-0.95865, 0.209004, -0.193152}, +{-0.0412347, 0.92731, 0.372016}, +{0.324157, 0.250873, -0.912132}, +{-0.248113, -0.228636, -0.941364}, +{-0.178278, -0.809909, -0.558806}, +{0.717251, -0.691423, -0.0865183}, +{-0.950332, 0.105907, -0.292664}, +{0.220015, 0.372745, 0.901474}, +{-0.876851, 0.055191, 0.477583}, +{-0.613126, -0.362667, 0.701819}, +{-0.66855, 0.0541595, 0.741692}, +{-0.152583, 0.463355, 0.872938}, +{-0.0526863, 0.119205, -0.991471}, +{0.848029, 0.459467, 0.264078}, +{0.360142, 0.932178, 0.0366424}, +{0.785941, 0.412695, -0.460412}, +{-0.108827, -0.39922, 0.910374}, +{0.966445, 0.0628985, 0.249053}, +{0.0200588, 0.657105, -0.753532}, +{-0.226779, 0.713063, 0.66341}, +{0.550349, -0.632884, -0.544585}, +{-0.0762218, 0.892488, -0.444585}, +{0.555455, -0.0191556, -0.831326}, +{0.10247, -0.330611, -0.938188}, +{0.704237, -0.425319, 0.568466}, +{0.143212, 0.33662, -0.930687}, +{-0.2142, -0.548569, -0.808202}, +{-0.0272693, 0.080357, -0.996393}, +{0.000371446, 0.726728, 0.686925}, +{-0.461627, -0.683031, -0.566012}, +{-0.939787, 0.0438012, 0.338942}, +{0.365693, -0.790935, 0.490602}, +{-0.97862, -0.191111, 0.0760241}, +{-0.919294, -0.0535573, 0.38991}, +{0.00754552, 0.0862503, 0.996245}, +{-0.79431, 0.586817, -0.157218}, +{0.688349, -0.452255, 0.567135}, +{0.136166, -0.905463, 0.401989}, +{-0.0166253, 0.586713, -0.809624}, +{0.411559, -0.833245, -0.369218}, +{0.223706, -0.0589692, 0.972871}, +{-0.796068, -0.466791, -0.385204}, +{-0.653786, 0.60573, -0.453491}, +{-0.268202, 0.963257, -0.0142502}, +{0.271745, 0.609036, 0.745137}, +{-0.357006, 0.314725, -0.879486}, +{-0.151997, -0.902489, 0.403001}, +{0.506257, 0.129592, -0.85259}, +{-0.020311, 0.387045, -0.921837}, +{-0.582477, -0.0412141, 0.811802}, +{-0.521465, 0.478639, -0.706385}, +{-0.296019, -0.435698, 0.850023}, +{-0.395479, -0.803993, -0.444063}, +{-0.175006, 0.937929, -0.299437}, +{0.838746, -0.544521, -0.00157376}, +{-0.110255, -0.0238806, -0.993616}, +{-0.667121, 0.712781, -0.216548}, +{0.299845, 0.920852, 0.249247}, +{-0.0515077, -0.99867, -0.00232009}, +{0.178735, -0.864259, 0.470223}, +{-0.468325, -0.874645, -0.125169}, +{0.852041, -0.276423, 0.44454}, +{-0.760551, 0.0322131, 0.648479}, +{0.409334, 0.318195, -0.855101}, +{0.608594, 0.0680627, -0.790557}, +{0.567129, 0.127259, -0.813738}, +{0.656535, -0.0345353, -0.753505}, +{0.877005, 0.226115, -0.42395}, +{-0.914003, -0.360397, -0.186311}, +{0.906787, -0.185747, 0.378465}, +{-0.842751, 0.412417, 0.345952}, +{0.744344, 0.664736, 0.0638628}, +{0.123714, 0.755327, -0.643565}, +{-0.989819, -0.117476, -0.0803607}, +{0.812842, -0.37025, 0.449669}, +{0.359762, -0.673066, -0.646184}, +{0.563714, 0.67623, 0.474277}, +{-0.32673, 0.365413, 0.87162}, +{0.355862, 0.851444, 0.385234}, +{-0.444099, 0.884856, -0.140734}, +{-0.518212, 0.275016, 0.809829}, +{-0.965081, -0.206953, -0.160588}, +{-0.48539, -0.693924, -0.531851}, +{0.310805, -0.807856, 0.500768}, +{0.521097, -0.797128, -0.305032}, +{0.806306, 0.291275, 0.514811}, +{-0.215554, -0.77776, 0.590446}, +{0.457502, 0.734079, -0.501817}, +{-0.606388, -0.657832, -0.446711}, +{-0.438057, -0.469783, -0.766427}, +{0.0512557, 0.632316, 0.773013}, +{-0.984274, 0.00527919, -0.176571}, +{0.707198, 0.619491, 0.340737}, +{-0.534898, -0.803109, 0.26249}, +{0.875431, 0.027402, 0.482565}, +{-0.722138, 0.668092, 0.179358}, +{-0.840659, 0.454499, 0.294487}, +{-0.36831, -0.836943, 0.404813}, +{-0.430301, 0.295783, -0.85285}, +{0.962924, 0.211972, 0.166867}, +{-0.125782, 0.0494547, 0.990824}, +{-0.0715944, -0.720002, 0.690269}, +{-0.0774431, 0.924758, -0.372594}, +{-0.806037, 0.462507, 0.369312}, +{-0.97373, 0.168624, 0.15302}, +{-0.536722, -0.834074, 0.127474}, +{-0.711738, -0.694911, -0.102603}, +{0.747344, 0.646129, 0.154903}, +{0.562866, -0.610824, 0.556845}, +{0.489621, 0.864246, -0.115543}, +{0.504884, 0.400291, -0.764761}, +{-0.948931, -0.301603, 0.0925529}, +{0.356807, 0.831749, 0.425302}, +{-0.317318, -0.89298, 0.319212}, +{0.768889, -0.458509, -0.445622}, +{-0.279466, -0.322849, 0.904249}, +{-0.978233, 0.191251, -0.0805206}, +{0.657112, 0.699075, -0.281954}, +{0.469821, 0.71135, 0.522732}, +{-0.92368, 0.247636, -0.29239}, +{0.497268, 0.75243, 0.431942}, +{-0.880444, 0.394607, 0.262878}, +{0.958675, 0.281137, 0.0436421}, +{0.817114, 0.576298, -0.0142972}, +{0.994933, 0.027068, 0.0968282}, +{-0.88562, -0.462646, 0.0404601}, +{-0.107159, -0.242041, -0.96433}, +{-0.91559, -0.394154, -0.0796051}, +{0.0940987, 0.96829, 0.231429}, +{-0.769799, -0.578461, 0.2698}, +{0.802056, 0.379614, 0.461086}, +{0.0948483, -0.986924, 0.130327}, +{0.959752, 0.264571, -0.0942274}, +{0.686637, -0.69076, -0.226673}, +{-0.0111353, 0.886364, 0.462856}, +{-0.687004, 0.530813, -0.49625}, +{-0.062462, -0.60022, -0.797392}, +{-0.907758, -0.418255, 0.0322052}, +{-0.940578, 0.301108, 0.156994}, +{0.413253, -0.908737, 0.0584695}, +{0.884226, -0.464548, -0.0483708}, +{0.637176, -0.344461, -0.689459}, +{-0.278267, -0.707751, -0.649351}, +{0.51137, 0.549188, 0.660979}, +{0.483002, -0.853932, -0.193672}, +{-0.557307, 0.628028, 0.54313}, +{0.894427, 0.0714784, 0.441464}, +{-0.0610692, -0.937543, -0.342466}, +{0.933624, -0.358155, 0.00843864}, +{-0.473702, -0.850981, -0.226801}, +{-0.155743, -0.98218, 0.1052}, +{-0.480714, 0.117182, 0.869012}, +{0.461441, 0.595996, -0.657161}, +{0.391121, -0.162678, 0.905848}, +{0.410735, 0.564103, -0.716299}, +{-0.226095, 0.88988, -0.396225}, +{0.388617, -0.154197, 0.908405}, +{0.577155, 0.478368, 0.661858}, +{-0.336802, -0.83204, -0.440765}, +{-0.334935, 0.913934, -0.229225}, +{0.728244, 0.0963185, -0.678516}, +{0.223291, -0.306122, -0.925435}, +{-0.329333, 0.744224, -0.581094}, +{0.587694, -0.362453, -0.723356}, +{-0.916875, 0.269037, 0.294889}, +{-0.264973, 0.679082, -0.684571}, +{0.782591, 0.0627382, -0.619366}, +{0.307547, 0.938237, -0.158514}, +{-0.559498, -0.348747, -0.75189}, +{0.195437, 0.138862, -0.970836}, +{0.203751, -0.945658, -0.253409}, +{0.531166, -0.357809, -0.768008}, +{-0.41101, -0.0314073, 0.911089}, +{-0.886193, 0.0153088, 0.463063}, +{-0.508897, -0.733116, -0.451181}, +{-0.885053, -0.0494792, -0.462854}, +{0.00193451, -0.478163, 0.878269}, +{0.289907, -0.458413, 0.840126}, +{-0.747995, -0.499798, 0.436698}, +{-0.384747, 0.922892, 0.0155168}, +{-0.406349, -0.913352, 0.0258396}, +{0.553947, 0.394189, 0.73332}, +{-0.407247, -0.871514, 0.273155}, +{-0.132593, 0.990013, 0.0478859}, +{0.151822, 0.233219, -0.960499}, +{-0.769176, 0.188715, 0.610536}, +{0.845749, 0.193445, 0.49728}, +{0.120775, -0.960782, 0.249622}, +{-0.318443, -0.76547, -0.559151}, +{0.51338, -0.858148, 0.00483224}, +{-0.78329, -0.525549, -0.332047}, +{-0.619844, -0.0336408, -0.784004}, +{0.525485, -0.739414, 0.420871}, +{0.643664, 0.561112, -0.520432}, +{-0.384694, -0.516673, -0.764892}, +{0.27667, 0.109823, 0.954669}, +{0.530839, 0.808903, 0.252758}, +{-0.0444572, -0.800562, -0.597598}, +{0.647347, 0.67988, -0.344537}, +{0.62075, -0.737366, -0.266386}, +{0.307871, -0.934327, -0.179576}, +{-0.721049, 0.673506, -0.162722}, +{0.456558, -0.0961293, 0.884485}, +{0.533682, -0.784538, 0.315726}, +{-0.0623512, 0.943435, -0.325641}, +{0.541308, 0.612609, 0.57593}, +{0.417872, -0.0060313, 0.908486}, +{0.00231839, -0.999821, 0.018791}, +{0.00322016, 0.491524, 0.870858}, +{-0.909238, -0.403173, 0.103622}, +{0.0617331, -0.548717, -0.833726}, +{-0.177169, -0.0902435, 0.980034}, +{-0.0151642, -0.956304, -0.291979}, +{-0.450303, 0.87543, 0.175639}, +{0.80663, -0.184426, 0.561547}, +{-0.172279, 0.951073, 0.256477}, +{0.507493, 0.183276, -0.841938}, +{0.997109, -0.00537923, -0.0757891}, +{0.880381, -0.401582, -0.252313}, +{-0.543401, -0.822325, 0.168812}, +{0.668734, 0.154025, -0.727372}, +{0.522773, 0.554341, 0.647622}, +{-0.117, 0.826837, -0.550138}, +{-0.903178, 0.372345, -0.213608}, +{-0.659975, -0.688639, -0.30035}, +{0.793478, -0.35465, -0.494586}, +{0.40331, 0.779916, 0.478615}, +{0.375859, 0.310938, 0.872953}, +{-0.208992, 0.0900884, -0.973759}, +{0.0698416, 0.725491, -0.684678}, +{-0.0041568, -0.521491, -0.853247}, +{0.827178, 0.423802, -0.369011}, +{-0.384463, -0.792356, 0.473667}, +{-0.762633, 0.013385, 0.646693}, +{0.0565688, 0.830027, -0.554848}, +{-0.540391, -0.63673, -0.550048}, +{0.534055, -0.836366, 0.123599}, +{0.613649, -0.0997385, -0.783254}, +{0.759973, 0.504826, 0.40938}, +{-0.466641, 0.299014, 0.832368}, +{0.597598, 0.503513, 0.623981}, +{-0.966721, -0.130458, 0.22007}, +{0.832234, -0.263242, -0.487945}, +{0.940425, -0.212844, 0.26514}, +{0.942508, -0.147517, 0.299861}, +{0.000406871, -0.96521, 0.261476}, +{0.110147, -0.772508, 0.625379}, +{-0.52056, -0.349817, -0.778875}, +{0.874266, -0.460366, -0.154021}, +{-0.773935, -0.44889, 0.446679}, +{-0.0855314, 0.376728, 0.922367}, +{0.586712, 0.575861, 0.569344}, +{0.327957, 0.935052, -0.134616}, +{-0.00718766, 0.862041, -0.506787}, +{0.9695, -0.15598, 0.189048}, +{-0.138613, 0.89377, -0.426569}, +{-0.631534, 0.419288, 0.652198}, +{-0.72035, -0.195532, 0.66548}, +{0.0446501, 0.184788, -0.981764}, +{-0.783654, 0.100316, -0.613044}, +{0.577455, 0.777446, 0.249245}, +{0.462889, -0.858585, 0.220376}, +{-0.841462, -0.384684, -0.379421}, +{-0.863628, 0.503862, 0.0164279}, +{0.339621, -0.826651, -0.44867}, +{0.220399, 0.0245116, 0.975102}, +{-0.543591, 0.188817, 0.817836}, +{0.16243, -0.957113, -0.239899}, +{0.876263, 0.399185, -0.26984}, +{0.179428, 0.316028, -0.931629}, +{0.115207, 0.740121, 0.662532}, +{-0.614899, 0.221476, 0.756867}, +{0.0361584, 0.187868, 0.981529}, +{-0.0958132, 0.575863, -0.811913}, +{0.389227, -0.338505, -0.85669}, +{0.85373, -0.0366796, -0.519423}, +{-0.902228, -0.359186, 0.238684}, +{-0.247044, 0.952445, -0.178375}, +{-0.219057, -0.878689, 0.42417}, +{-0.382045, -0.0716048, -0.921365}, +{-0.00891314, -0.992079, -0.125302}, +{-0.343547, 0.86154, -0.373797}, +{0.769489, -0.399872, 0.497985}, +{0.834522, 0.033065, -0.549981}, +{0.0293713, 0.999506, 0.0111891}, +{0.767002, 0.576767, 0.281156}, +{0.617512, -0.440566, -0.651599}, +{0.858539, 0.184531, 0.478392}, +{0.735985, -0.0660194, -0.673771}, +{0.169436, 0.83516, 0.523258}, +{-0.971435, -0.100976, -0.21475}, +{-0.529143, 0.385999, -0.755653}, +{-0.26917, 0.296692, 0.916254}, +{0.573012, 0.638289, -0.514046}, +{-0.959353, 0.186178, -0.212083}, +{0.754692, 0.501912, -0.42252}, +{-0.44849, -0.730648, 0.514791}, +{-0.263596, -0.898799, 0.350254}, +{0.174097, -0.900129, -0.399322}, +{-0.641445, -0.523962, -0.560368}, +{0.338258, -0.907212, -0.250094}, +{0.299755, 0.306285, 0.903513}, +{-0.280233, 0.505717, -0.815917}, +{-0.507261, -0.696009, 0.508191}, +{-0.180303, 0.599335, 0.779929}, +{-0.256069, -0.783036, -0.566819}, +{0.392981, -0.850156, -0.35043}, +{-0.496584, -0.863631, -0.0868624}, +{0.660183, -0.751064, 0.00783591}, +{-0.615304, -0.713558, -0.335015}, +{0.183484, 0.938736, 0.291732}, +{0.630575, -0.255534, -0.732856}, +{0.370447, -0.904492, -0.211337}, +{0.665919, 0.574484, -0.475941}, +{-0.00951968, 0.796251, -0.604892}, +{-0.8817, 0.451421, -0.137205}, +{0.156115, -0.924411, -0.347983}, +{-0.180785, 0.95709, -0.226486}, +{0.310889, -0.0509561, -0.949079}, +{-0.00859597, 0.259077, -0.965818}, +{0.976049, 0.159106, -0.148371}, +{-0.245468, 0.964076, -0.101498}, +{-0.368103, 0.838402, 0.401974}, +{0.0955832, 0.672777, 0.733645}, +{-0.598686, 0.451949, -0.6613}, +{-0.258922, 0.638277, 0.724956}, +{-0.540547, 0.454222, 0.70816}, +{0.271563, -0.0873829, 0.958446}, +{0.219411, -0.685216, 0.694505}, +{-0.102656, -0.933706, -0.343008}, +{0.786511, -0.00580375, 0.617549}, +{0.364143, 0.588379, -0.721949}, +{0.36775, -0.313753, 0.875396}, +{0.947864, -0.317536, -0.0269145}, +{0.939337, 0.337765, -0.0596795}, +{0.568666, -0.538615, 0.621701}, +{0.748828, -0.644929, 0.152719}, +{0.523342, -0.24516, 0.816094}, +{0.733229, 0.586715, -0.343718}, +{-0.797999, -0.542301, -0.262881}, +{0.671106, -0.531998, -0.516328}, +{-0.888974, -0.301074, -0.345079}, +{0.206924, 0.550319, 0.808907}, +{-0.559181, 0.289254, 0.776948}, +{-0.0938298, -0.697217, -0.710693}, +{0.623535, 0.523176, 0.580939}, +{-0.707511, 0.062856, 0.703902}, +{-0.646637, -0.700816, 0.301194}, +{-0.525121, 0.737893, -0.423984}, +{0.15164, 0.988025, 0.0284771}, +{-0.86092, 0.179796, -0.475909}, +{-0.639724, 0.648446, -0.412638}, +{-0.997179, -0.0743239, -0.0104461}, +{-0.117534, -0.518366, -0.847043}, +{-0.347103, -0.28328, -0.89402}, +{0.0708557, 0.695661, -0.714867}, +{-0.45203, -0.841223, 0.296669}, +{-0.111283, 0.989303, -0.0943161}, +{-0.0670789, -0.984849, 0.159915}, +{0.586661, -0.191435, 0.786881}, +{0.0423296, -0.630171, -0.775301}, +{-0.316093, -0.812612, 0.48964}, +{0.874997, -0.398802, -0.274477}, +{0.506497, 0.79839, 0.32563}, +{0.084214, -0.532482, 0.842242}, +{0.907691, 0.182187, -0.378029}, +{-0.126219, 0.0951336, 0.98743}, +{0.0281629, 0.923537, -0.382475}, +{-0.178284, 0.724697, 0.665604}, +{0.747961, -0.653504, 0.116135}, +{0.325684, 0.0777026, 0.94228}, +{-0.674231, 0.152116, -0.722685}, +{0.662966, 0.351887, -0.660796}, +{0.285848, -0.74964, -0.596934}, +{0.216846, 0.699646, 0.680789}, +{-0.771803, 0.331673, -0.542506}, +{-0.627113, -0.77026, -0.115883}, +{0.389815, -0.818306, -0.422397}, +{0.238385, -0.868329, 0.434944}, +{-0.999817, 0.0161285, 0.0103098}, +{-0.904934, 0.39839, -0.149598}, +{-0.185575, -0.0850114, 0.978946}, +{0.131874, 0.487958, -0.862848}, +{-0.174371, 0.718704, -0.673097}, +{-0.119936, -0.846542, 0.518634}, +{0.097234, 0.896331, -0.432592}, +{-0.832144, -0.265247, -0.487012}, +{0.515418, 0.0406872, 0.855973}, +{0.978624, 0.033505, -0.202908}, +{-0.272298, 0.348537, -0.89687}, +{0.59168, 0.638328, 0.492394}, +{0.840538, 0.365087, 0.400259}, +{0.77142, 0.636009, -0.0200682}, +{0.977858, -0.12861, -0.165087}, +{0.847231, 0.19858, 0.492713}, +{0.438245, -0.614395, 0.656095}, +{-0.650568, -0.74577, -0.143489}, +{0.0600391, -0.283202, -0.957179}, +{0.102575, -0.53136, -0.840913}, +{0.972715, 0.126757, -0.194316}, +{0.456117, 0.889347, 0.0319306}, +{-0.159025, -0.896541, -0.413431}, +{0.691973, -0.701302, -0.171316}, +{-0.633769, -0.676413, -0.375236}, +{0.320356, -0.942328, 0.0969067}, +{-0.921228, -0.387783, 0.0310236}, +{-0.342873, 0.204252, 0.916907}, +{-0.247699, 0.914163, 0.320859}, +{0.734626, 0.556813, -0.387664}, +{0.475471, -0.363521, -0.801112}, +{0.693229, -0.0917919, -0.714848}, +{0.379962, -0.746411, -0.54635}, +{0.383612, -0.551422, 0.740794}, +{-0.620745, 0.754373, 0.213533}, +{-0.605588, -0.743471, 0.283748}, +{0.231051, -0.972424, 0.0317357}, +{-0.812267, -0.524599, 0.254988}, +{-0.718837, 0.461949, -0.519496}, +{0.0850343, -0.238328, 0.967455}, +{-0.697359, -0.44253, -0.563789}, +{-0.557382, -0.332204, 0.760898}, +{-0.658554, -0.720385, 0.217605}, +{0.868968, -0.256146, -0.423419}, +{0.745286, 0.43461, 0.505631}, +{-0.419673, -0.694695, -0.584186}, +{0.0133172, 0.437802, -0.898973}, +{-0.540184, -0.0200985, -0.841307}, +{0.00857848, -0.0758923, -0.997079}, +{0.42252, -0.430103, 0.797802}, +{0.258198, 0.921777, -0.289243}, +{0.453887, -0.775313, 0.439176}, +{-0.481425, 0.792202, 0.375027}, +{-0.908885, -0.215002, -0.357355}, +{0.880371, 0.333997, 0.336739}, +{-0.685781, 0.724493, 0.0693802}, +{0.75287, -0.521899, 0.401008}, +{-0.555896, -0.584391, -0.591157}, +{0.926853, -0.0787792, 0.367067}, +{0.599096, 0.790032, 0.130129}, +{-0.0800299, -0.961268, 0.26374}, +{-0.105976, 0.132551, -0.985494}, +{-0.779735, 0.00896166, -0.626045}, +{-0.362426, -0.772916, 0.520814}, +{0.0904422, -0.442068, 0.89241}, +{0.0510809, 0.150643, -0.987268}, +{0.534503, -0.457381, 0.71071}, +{0.379797, 0.207182, 0.901571}, +{0.832241, -0.0720661, 0.54971}, +{-0.64432, 0.30923, -0.699448}, +{0.562336, -0.646135, 0.516031}, +{-0.98079, -0.194263, 0.0176693}, +{-0.762813, -0.645052, -0.0449845}, +{-0.0507844, -0.312343, -0.948611}, +{-0.489643, 0.629634, -0.603167}, +{-0.307913, 0.566793, 0.764156}, +{0.649206, 0.425418, -0.630517}, +{-0.96375, 0.2654, -0.0273566}, +{0.441283, -0.87316, 0.20703}, +{0.2193, 0.686705, -0.693069}, +{-0.631308, 0.222781, 0.742845}, +{-0.253389, 0.702809, 0.664721}, +{0.462036, -0.860391, -0.21506}, +{0.413191, -0.408262, 0.814}, +{-0.752506, 0.466437, 0.464942}, +{0.912789, -0.357676, 0.197191}, +{-0.763851, 0.566754, -0.308742}, +{0.356875, -0.747708, 0.559975}, +{0.632369, 0.416689, 0.653054}, +{-0.394489, 0.758273, 0.519038}, +{0.641198, -0.739367, 0.205429}, +{0.924303, -0.0257618, 0.38079}, +{-0.236468, -0.350615, 0.906175}, +{0.676791, 0.707813, -0.202373}, +{0.966935, -0.0375242, 0.252246}, +{-0.850229, -0.443346, 0.283823}, +{-0.396208, 0.80668, -0.438504}, +{-0.711691, -0.690115, 0.131288}, +{-0.919239, -0.367192, -0.142021}, +{0.446242, 0.451553, -0.772637}, +{-0.777904, -0.15271, -0.609545}, +{-0.0499683, -0.752191, 0.657047}, +{-0.515899, -0.833077, -0.199578}, +{-0.137638, -0.618067, 0.773982}, +{0.386184, 0.590225, 0.70887}, +{0.441418, 0.739932, -0.507594}, +{0.801207, -0.271621, 0.533188}, +{-0.724752, -0.666212, -0.175772}, +{0.484562, -0.739167, -0.467796}, +{0.151947, -0.770387, 0.619207}, +{0.807826, 0.53662, 0.243836}, +{0.0160061, -0.228664, -0.973374}, +{-0.0725785, -0.544313, 0.835737}, +{-0.598839, 0.0976366, 0.794895}, +{0.132611, 0.466952, -0.874283}, +{0.62406, -0.52951, -0.574602}, +{-0.591797, 0.533061, -0.604667}, +{0.922513, -0.382425, 0.0521688}, +{-0.757578, -0.175438, 0.628727}, +{0.0763379, 0.737294, 0.671245}, +{-0.908078, -0.0915811, 0.408665}, +{0.590709, 0.590845, -0.549513}, +{-0.114357, 0.907418, 0.40437}, +{0.0515411, 0.891906, 0.449274}, +{-0.467265, 0.638907, 0.611115}, +{-0.213032, 0.854247, 0.474214}, +{-0.274709, 0.949345, -0.152574}, +{0.0149963, -0.836571, -0.547654}, +{-0.300591, -0.898605, -0.319616}, +{0.0898673, -0.960913, -0.261858}, +{-0.554931, 0.489267, -0.672807}, +{0.631752, 0.613657, 0.473618}, +{0.581176, 0.508307, -0.635499}, +{-0.309478, -0.918809, -0.244978}, +{-0.876036, -0.227938, 0.424976}, +{0.206989, -0.435035, 0.876299}, +{0.885297, 0.21622, 0.411701}, +{-0.521121, -0.761056, -0.386297}, +{0.54457, -0.156297, 0.824023}, +{-0.347625, 0.276678, -0.895883}, +{0.74808, -0.348012, -0.565035}, +{-0.965178, -0.0476816, -0.257212}, +{-0.336649, 0.266991, 0.902986}, +{-0.0882738, -0.976907, 0.194578}, +{-0.268958, 0.228098, -0.935753}, +{-0.182542, -0.710819, -0.679275}, +{-0.112118, 0.403014, -0.9083}, +{0.936329, 0.300868, 0.181016}, +{-0.181525, -0.971769, 0.150712}, +{-0.277529, -0.883986, 0.376226}, +{-0.700384, -0.0163547, 0.713579}, +{0.702325, 0.633504, -0.324673}, +{0.527775, -0.284538, 0.800307}, +{-0.44733, -0.367775, -0.815253}, +{-0.60098, -0.555968, -0.574215}, +{0.306122, -0.119952, -0.944405}, +{-0.189767, -0.981238, -0.0340667}, +{-0.338754, -0.930942, -0.136358}, +{-0.460797, 0.679859, 0.570488}, +{0.497971, -0.818553, 0.286351}, +{0.314076, 0.947068, -0.0664723}, +{-0.11104, 0.233948, 0.965887}, +{-0.979098, 0.101824, -0.176064}, +{0.12189, 0.92466, -0.360758}, +{-0.45518, 0.847691, 0.272456}, +{-0.621655, -0.689415, -0.371823}, +{0.0823722, 0.788344, -0.609695}, +{0.0613788, 0.581846, -0.81098}, +{0.663673, 0.00572015, -0.748001}, +{-0.776763, -0.628589, 0.038918}, +{0.560615, 0.196226, -0.804492}, +{0.537971, 0.712673, 0.450204}, +{-0.0863274, -0.831108, -0.54937}, +{0.0238151, 0.264817, -0.964005}, +{0.502303, 0.728614, -0.465632}, +{0.412646, -0.6123, -0.674397}, +{0.656336, 0.218424, 0.722159}, +{-0.654761, -0.605215, -0.452772}, +{-0.87275, -0.487838, -0.0179179}, +{-0.195067, 0.979401, 0.0521873}, +{0.346486, 0.808786, -0.475198}, +{0.429815, -0.896132, 0.110482}, +{-0.660316, -0.184504, 0.72797}, +{-0.724236, -0.154172, 0.672096}, +{-0.981784, 0.117084, 0.149641}, +{-0.0172067, 0.388196, 0.921416}, +{-0.769422, 0.634006, 0.077622}, +{-0.610489, -0.404893, -0.680709}, +{0.793901, 0.54637, -0.266835}, +{-0.575661, -0.593147, 0.562842}, +{0.700949, 0.71315, -0.0093296}, +{0.454319, 0.863401, 0.219391}, +{-0.00211086, -0.148559, 0.988901}, +{0.458001, -0.247984, 0.853662}, +{-0.842111, -0.478175, 0.249394}, +{-0.0251108, -0.694837, 0.718729}, +{0.119338, -0.929992, -0.347667}, +{-0.0229745, -0.912507, 0.408415}, +{-0.482469, -0.644423, 0.593247}, +{-0.452778, 0.817907, -0.354994}, +{-0.786412, -0.12755, 0.60439}, +{0.87464, 0.439447, -0.204674}, +{0.485918, 0.829483, 0.275394}, +{0.759681, -0.257372, -0.597198}, +{0.786305, -0.564795, -0.25046}, +{-0.247723, -0.675551, -0.694452}, +{0.482498, 0.163919, 0.860422}, +{-0.446129, -0.587375, -0.675248}, +{0.34874, -0.932196, 0.0969013}, +{0.411562, -0.18352, -0.892713}, +{-0.943731, -0.328216, 0.0405633}, +{0.0792567, 0.235715, 0.968585}, +{0.476319, -0.874296, 0.0934158}, +{-0.173073, -0.984575, 0.0256383}, +{0.521598, -0.665949, -0.533336}, +{-0.993085, -0.0492451, 0.106574}, +{-0.615467, 0.642767, 0.456126}, +{-0.12307, 0.967327, -0.22166}, +{-0.836658, 0.325703, 0.440365}, +{0.826517, 0.0274141, 0.562244}, +{-0.370971, -0.156905, 0.915293}, +{-0.190605, -0.84248, 0.503882}, +{0.423659, -0.131449, -0.896233}, +{-0.246442, -0.918876, 0.308112}, +{-0.436812, 0.563503, 0.701185}, +{-0.589822, 0.360203, -0.722747}, +{-0.150729, 0.971687, -0.181949}, +{0.182369, -0.259065, 0.948486}, +{0.565484, -0.824337, 0.0264015}, +{-0.288871, 0.837539, 0.463769}, +{-0.179551, 0.127951, -0.975392}, +{-0.7303, -0.232248, -0.642435}, +{-0.106135, 0.747645, 0.655563}, +{-0.815409, -0.307189, -0.490655}, +{-0.180687, -0.650094, -0.738059}, +{0.953687, -0.267219, 0.138109}, +{-0.574301, 0.77271, -0.270367}, +{0.170553, -0.629949, -0.757678}, +{-0.0443653, -0.698536, 0.714199}, +{0.752387, -0.609843, -0.249009}, +{-0.455843, -0.0791897, -0.886531}, +{0.0160565, 0.311812, 0.950008}, +{-0.167538, -0.309967, -0.935869}, +{0.357651, -0.331299, -0.873113}, +{-0.383063, -0.413339, 0.826083}, +{-0.133048, 0.267014, -0.954464}, +{0.753921, 0.628755, 0.190447}, +{0.978838, 0.187512, 0.0819446}, +{0.598604, 0.445371, 0.665822}, +{0.903263, -0.0863548, -0.420309}, +{0.640457, 0.189057, 0.74436}, +{0.29483, 0.21187, -0.931765}, +{0.919899, 0.128347, 0.370558}, +{0.918373, -0.294068, 0.264792}, +{-0.328636, 0.589816, 0.737642}, +{0.210884, 0.966106, -0.148889}, +{-0.440904, 0.822707, -0.358828}, +{-0.792197, 0.421927, -0.44091}, +{-0.0212758, -0.994748, -0.100121}, +{-0.501326, 0.186439, -0.844934}, +{-0.162997, -0.869702, 0.465887}, +{0.445067, -0.879534, 0.168331}, +{0.491205, -0.271261, 0.827729}, +{-0.755418, 0.208842, -0.621071}, +{-0.260741, -0.950779, -0.167431}, +{0.392083, 0.514629, -0.762514}, +{0.920366, 0.265734, 0.286901}, +{0.3935, -0.604221, 0.692874}, +{0.962161, -0.225074, -0.153585}, +{0.305594, -0.664454, -0.681992}, +{-0.0018586, 0.848349, -0.529435}, +{0.026884, 0.991318, 0.12871}, +{-0.0209917, 0.939844, -0.340957}, +{0.509332, 0.790323, -0.340546}, +{-0.26032, 0.455164, -0.851504}, +{0.22941, 0.866819, 0.442714}, +{-0.42561, -0.692639, 0.58233}, +{-0.369829, -0.849838, 0.375504}, +{0.102444, 0.146254, 0.983928}, +{-0.864415, 0.443831, -0.236221}, +{-0.363447, -0.427236, -0.827874}, +{0.498062, 0.311563, -0.809236}, +{0.0913729, 0.878917, 0.46814}, +{-0.332729, -0.937897, -0.0981852}, +{0.992008, -0.117579, 0.0457646}, +{-0.4764, 0.239668, -0.845933}, +{0.565042, 0.0310445, -0.824478}, +{-0.986771, 0.141368, 0.0793623}, +{-0.644377, 0.0889339, -0.759519}, +{0.92541, 0.378502, -0.0187607}, +{0.598127, 0.757229, 0.262391}, +{0.390511, -0.427914, -0.815101}, +{-0.787185, -0.101801, 0.608257}, +{-0.335048, -0.588411, -0.735878}, +{0.986674, 0.124934, -0.104236}, +{-0.5356, 0.54016, -0.649122}, +{-0.0470512, 0.723962, -0.688233}, +{-0.538927, 0.771496, 0.338158}, +{0.31158, -0.901519, 0.300304}, +{0.600644, 0.620407, -0.504303}, +{-0.34158, -0.534006, 0.773408}, +{0.430472, -0.532101, 0.729083}, +{0.232696, -0.0666136, -0.970266}, +{-0.171712, 0.298988, -0.93868}, +{0.546842, -0.257683, -0.796595}, +{0.32322, 0.668453, 0.66985}, +{0.845307, 0.431327, -0.315299}, +{0.717115, 0.308481, 0.624969}, +{-0.139106, -0.98823, -0.0636408}, +{-0.468382, -0.277982, 0.838657}, +{0.337845, -0.403722, 0.850217}, +{0.815311, -0.472265, 0.335013}, +{-0.678879, -0.656619, -0.328597}, +{-0.194051, 0.971232, -0.138028}, +{0.785586, -0.601124, 0.146644}, +{-0.230019, -0.630358, -0.741444}, +{-0.581121, 0.813817, 0.000576663}, +{-0.732469, 0.399189, 0.551487}, +{-0.853814, -0.0377942, 0.519204}, +{-0.997597, -0.0141114, 0.0678357}, +{0.816389, 0.180257, 0.54865}, +{-0.451547, -0.888382, 0.082964}, +{-0.677818, -0.709964, -0.191086}, +{-0.659039, -0.147187, -0.737566}, +{-0.535071, -0.835584, -0.124492}, +{0.243739, -0.114865, -0.963015}, +{-0.909375, -0.345083, -0.232281}, +{-0.644468, -0.556518, -0.524356}, +{-0.213441, -0.78817, -0.577262}, +{0.00536965, 0.906626, 0.4219}, +{-0.324016, 0.507467, 0.798431}, +{-0.762923, 0.626552, 0.159317}, +{0.88641, -0.248541, -0.390519}, +{0.586276, 0.0431029, 0.808964}, +{-0.593792, 0.0495092, -0.803094}, +{-0.225318, -0.316146, -0.921566}, +{0.660473, 0.223225, -0.7169}, +{-0.678025, -0.61397, -0.404132}, +{0.211066, -0.387393, -0.897428}, +{-0.0400299, 0.603656, -0.796239}, +{-0.613117, -0.0535484, -0.788175}, +{-0.797526, 0.0236241, -0.602821}, +{0.0487369, -0.858526, 0.510449}, +{-0.802202, 0.571132, -0.174013}, +{0.405569, -0.907003, -0.113398}, +{-0.286258, 0.0117484, 0.958081}, +{0.412843, -0.386642, 0.824663}, +{-0.0189412, 0.598376, 0.800991}, +{-0.629613, 0.47354, 0.615912}, +{0.568811, 0.182868, -0.801881}, +{-0.508431, 0.793644, -0.334107}, +{0.686108, -0.354731, -0.635155}, +{0.0163038, 0.566737, 0.823737}, +{0.880722, 0.282481, 0.380176}, +{-0.235871, -0.467574, -0.851903}, +{0.040288, -0.708124, 0.704938}, +{0.67195, -0.410241, 0.616592}, +{-0.499548, -0.43564, -0.748779}, +{-0.590898, -0.329174, -0.736535}, +{-0.815288, 0.575679, -0.0624431}, +{0.811606, -0.543352, -0.214625}, +{-0.786371, 0.0726878, 0.613463}, +{0.18561, 0.438509, 0.879351}, +{-0.60468, 0.125098, -0.786583}, +{0.65781, 0.409914, 0.631867}, +{-0.834536, 0.0237776, 0.550441}, +{0.512255, 0.835125, -0.200404}, +{-0.35771, 0.233291, -0.904223}, +{0.297328, 0.885598, 0.356808}, +{0.804627, -0.0847723, 0.587697}, +{-0.400933, -0.750469, -0.525405}, +{-0.209164, 0.191928, 0.958861}, +{-0.61364, -0.789067, -0.0286309}, +{-0.0941753, -0.919112, -0.382576}, +{-0.551848, 0.732281, -0.399034}, +{0.242897, 0.760733, 0.601902}, +{0.77338, -0.278636, -0.569425}, +{0.37735, -0.631143, 0.677691}, +{0.594082, 0.111438, 0.796648}, +{0.834667, 0.487633, 0.256018}, +{-0.131865, -0.138596, 0.981531}, +{0.40084, -0.681444, 0.612341}, +{-0.895388, 0.000361151, -0.445287}, +{0.356267, 0.316992, 0.878971}, +{0.994906, -0.0491068, -0.0880428}, +{-0.915295, -0.309169, -0.258167}, +{-0.346067, 0.883941, -0.314462}, +{-0.349293, 0.775755, 0.525547}, +{0.00638914, -0.997967, 0.0634046}, +{-0.411102, -0.90309, -0.124195}, +{-0.286838, -0.947916, 0.138486}, +{-0.992929, 0.111422, -0.0409538}, +{-0.768254, 0.212469, 0.603857}, +{0.0379018, 0.700003, -0.713134}, +{-0.373745, -0.924472, -0.075281}, +{-0.984779, -0.0922161, -0.147329}, +{-0.274121, 0.545258, -0.792181}, +{-0.142584, 0.464022, -0.874273}, +{0.22077, -0.516169, 0.827545}, +{-0.0423619, 0.353185, 0.934594}, +{-0.610409, -0.641137, 0.465128}, +{-0.776532, -0.577681, -0.25156}, +{0.471752, 0.855565, -0.213212}, +{0.00548194, -0.348559, 0.937271}, +{0.114943, 0.980386, 0.160098}, +{-0.43378, -0.901011, 0.00368596}, +{-0.197734, -0.536083, -0.820681}, +{-0.0866809, -0.123389, 0.988565}, +{0.337222, -0.915315, 0.220181}, +{0.807693, -0.0128885, -0.589462}, +{0.00730681, 0.911337, -0.411596}, +{-0.637418, -0.223376, -0.737429}, +{-0.150401, 0.349636, 0.924735}, +{-0.220253, -0.717422, -0.660904}, +{0.648102, 0.207573, -0.732719}, +{-0.425853, -0.886892, -0.179088}, +{-0.283374, 0.656596, 0.698985}, +{0.525362, -0.404039, -0.74883}, +{0.46541, -0.783305, -0.412101}, +{-0.437021, -0.547113, -0.713919}, +{0.384387, -0.888066, -0.252162}, +{-0.618659, -0.668758, 0.41234}, +{-0.774279, 0.342848, 0.531929}, +{-0.347088, 0.436636, -0.829987}, +{0.0303561, 0.85204, 0.522596}, +{0.0249281, -0.783841, 0.620462}, +{0.467256, -0.590633, -0.657894}, +{0.925161, 0.0136609, 0.37933}, +{0.392199, 0.467066, 0.792483}, +{-0.746546, 0.37082, -0.552415}, +{0.921672, 0.152588, -0.356703}, +{0.378443, 0.0253665, 0.925277}, +{0.574468, -0.802903, -0.159162}, +{0.0566599, -0.96409, -0.259463}, +{0.479905, -0.431077, -0.764109}, +{0.140711, -0.404712, -0.903553}, +{-0.291479, 0.941784, 0.167581}, +{-0.14342, -0.0459028, 0.988597}, +{0.215767, 0.866765, -0.449625}, +{0.0491895, -0.367383, -0.928768}, +{0.560423, -0.200899, 0.803471}, +{-0.430966, -0.563579, 0.704732}, +{-0.461726, 0.563289, 0.685212}, +{-0.453623, 0.615159, 0.64483}, +{0.789562, 0.109216, -0.603874}, +{-0.825065, -0.363472, -0.432615}, +{-0.896866, -0.430535, -0.101343}, +{-0.886107, 0.321064, 0.334265}, +{-0.747928, -0.62929, -0.211182}, +{0.203859, 0.97371, 0.101635}, +{0.92602, 0.364218, -0.0991587}, +{0.180679, -0.598835, 0.780225}, +{-0.248555, 0.910633, -0.330102}, +{-0.543452, 0.812075, -0.212587}, +{0.170156, 0.0396685, -0.984618}, +{0.39274, -0.357595, 0.847279}, +{0.52032, 0.294712, 0.801506}, +{0.341262, 0.908976, -0.239381}, +{0.270391, 0.953021, 0.136526}, +{-0.217978, 0.568213, 0.793486}, +{-0.412558, 0.073954, -0.907924}, +{0.307075, 0.927615, -0.212686}, +{-0.212229, 0.708936, 0.672583}, +{-0.45875, -0.815075, -0.353837}, +{0.160656, 0.907088, 0.389078}, +{-0.00419196, -0.821084, 0.570792}, +{-0.931442, 0.326992, -0.159665}, +{0.231185, -0.585436, 0.777057}, +{0.912655, 0.35412, 0.204107}, +{0.533371, -0.63685, 0.556721}, +{0.658644, 0.370608, 0.654857}, +{-0.874738, 0.451156, 0.176894}, +{0.215074, 0.462786, 0.859984}, +{0.272735, 0.954884, 0.117525}, +{0.539459, -0.828286, 0.151413}, +{-0.744302, 0.343906, -0.572489}, +{0.223486, -0.16372, -0.960859}, +{0.731851, 0.0120385, 0.681358}, +{-0.613769, 0.73608, 0.285437}, +{-0.711564, 0.698039, -0.0801108}, +{-0.450962, 0.758978, -0.469665}, +{-0.256135, -0.909277, 0.32804}, +{0.174452, 0.815542, -0.551777}, +{0.643331, 0.0034245, 0.765581}, +{0.0805686, 0.178396, 0.980655}, +{-0.0328732, 0.273511, 0.961307}, +{-0.278121, -0.532233, -0.799611}, +{-0.661443, 0.72171, 0.204028}, +{0.483429, -0.351741, -0.801608}, +{-0.489138, -0.416075, 0.766567}, +{0.750355, -0.60281, 0.271269}, +{-0.673634, 0.239467, -0.699195}, +{0.883011, -0.00583524, -0.469316}, +{-0.935313, 0.225378, 0.272753}, +{-0.440468, -0.866225, 0.235885}, +{-0.132566, -0.920511, 0.367541}, +{-0.520558, -0.677483, 0.51965}, +{-0.398604, 0.571019, -0.717672}, +{-0.348135, 0.911745, 0.217997}, +{-0.464791, 0.866315, -0.182942}, +{-0.309121, 0.643183, -0.700542}, +{0.138048, 0.98358, 0.116247}, +{-0.450234, 0.380369, -0.807842}, +{0.0682496, -0.741902, -0.667025}, +{0.180577, 0.960868, 0.210057}, +{0.868485, 0.41197, 0.275707}, +{0.428712, 0.843418, 0.323809}, +{-0.844003, -0.536222, 0.0111501}, +{-0.430226, -0.166097, -0.887309}, +{0.16515, 0.537304, -0.827061}, +{-0.441149, -0.324038, -0.836891}, +{-0.519058, -0.582707, 0.625325}, +{-0.0282464, 0.767153, 0.640842}, +{-0.435503, 0.308068, 0.845832}, +{0.76222, 0.624343, 0.17093}, +{0.517322, -0.42704, 0.74163}, +{0.385974, -0.279605, -0.879116}, +{0.620423, 0.746939, -0.239075}, +{-0.543439, 0.120607, 0.830739}, +{-0.771714, -0.279444, -0.571287}, +{0.182673, 0.871852, -0.454428}, +{-0.0161641, 0.310278, -0.950508}, +{0.384668, -0.132719, -0.913464}, +{-0.498985, -0.824184, 0.267833}, +{0.175717, 0.178695, 0.968086}, +{-0.641946, -0.664147, 0.383163}, +{0.551132, -0.828126, 0.102281}, +{-0.0699595, 0.0215918, -0.997316}, +{-0.912406, 0.401465, -0.0796312}, +{0.206297, -0.0317125, -0.977975}, +{-0.297909, 0.953548, 0.0446842}, +{0.818555, -0.473194, -0.325661}, +{0.314708, 0.00388889, 0.949181}, +{0.506442, -0.29813, -0.809096}, +{0.495412, -0.684703, 0.534555}, +{-0.336273, 0.540399, 0.771291}, +{-0.575167, -0.358276, 0.735406}, +{0.964002, 0.22519, -0.141386}, +{0.284677, 0.552276, 0.78355}, +{0.0353088, -0.135695, -0.990121}, +{-0.218563, 0.15554, 0.963347}, +{0.691999, 0.0712892, 0.718369}, +{0.237863, -0.926112, 0.292809}, +{-0.738214, -0.0234752, -0.674158}, +{0.112006, -0.814119, 0.569794}, +{0.54568, -0.0660479, -0.835387}, +{0.770386, 0.208305, 0.60259}, +{0.144564, 0.670373, -0.727806}, +{-0.638789, 0.244064, -0.729644}, +{-0.0557894, 0.956147, 0.287524}, +{0.18352, -0.802197, 0.568155}, +{0.214493, -0.295013, -0.931107}, +{-0.0935418, 0.804638, 0.586351}, +{-0.619843, 0.784678, -0.00862755}, +{0.742657, 0.146014, -0.653559}, +{0.20729, -0.693869, -0.68962}, +{-0.60053, 0.636934, 0.483403}, +{-0.0595036, -0.386012, 0.920573}, +{-0.832089, -0.388453, 0.395894}, +{0.624879, 0.773172, 0.108309}, +{0.320475, -0.947169, -0.0129107}, +{-0.136695, -0.594284, 0.792554}, +{0.195973, -0.301336, -0.933162}, +{0.451708, 0.791781, 0.411149}, +{0.478044, -0.544795, -0.688965}, +{-0.425127, 0.00518396, -0.905119}, +{0.796824, 0.554413, 0.240202}, +{0.591784, -0.32261, -0.738725}, +{0.148368, -0.724569, 0.673043}, +{0.493541, -0.589718, 0.639258}, +{-0.700782, -0.713372, -0.002346}, +{0.161862, -0.375768, 0.912469}, +{0.466338, 0.498572, -0.730722}, +{-0.95239, -0.142777, 0.269384}, +{0.280491, 0.780976, 0.558033}, +{-0.0644663, 0.480136, -0.874822}, +{-0.00557685, 0.995103, -0.0986872}, +{0.811447, 0.494228, -0.311918}, +{0.336124, 0.60497, 0.721825}, +{0.900026, 0.435184, 0.0238484}, +{-0.953769, 0.298459, -0.0353197}, +{0.238119, 0.178422, -0.954707}, +{-0.948766, 0.204587, 0.240807}, +{-0.497572, -0.483306, 0.720304}, +{-0.188148, 0.795118, -0.576531}, +{0.66834, -0.652834, -0.356551}, +{-0.0163034, 0.974261, 0.224831}, +{-0.566056, 0.814017, 0.130222}, +{0.820034, 0.535613, -0.201652}, +{0.528192, -0.113119, 0.841556}, +{-0.553001, 0.539679, 0.634773}, +{-0.686843, 0.412795, 0.598203}, +{-0.914808, 0.284273, -0.286905}, +{-0.487446, -0.807486, 0.332209}, +{-0.069919, -0.967037, -0.24485}, +{-0.993992, 0.00819683, -0.109146}, +{0.9108, 0.360851, 0.200574}, +{0.18496, -0.864617, -0.467148}, +{-0.446201, -0.4956, -0.745175}, +{-0.29924, 0.598129, -0.743436}, +{-0.196428, 0.83594, 0.512466}, +{0.29949, -0.455354, 0.838426}, +{-0.356353, 0.899429, 0.253062}, +{-0.00155663, 0.46074, -0.887534}, +{-0.176263, -0.981095, 0.079894}, +{0.0215919, 0.985301, 0.169459}, +{0.43701, 0.587931, 0.680705}, +{0.796599, 0.455765, -0.397126}, +{-0.108254, -0.803447, -0.585453}, +{-0.0768764, -0.650086, 0.755961}, +{0.386048, -0.565917, 0.728495}, +{0.382259, 0.176853, -0.906974}, +{-0.266249, -0.963846, 0.0106175}, +{0.98658, 0.157269, -0.0438794}, +{0.0149229, -0.991192, 0.13159}, +{-0.816669, -0.309479, 0.487108}, +{-0.79851, -0.586448, 0.13587}, +{0.216832, 0.76797, -0.602666}, +{0.89174, 0.0189059, -0.452153}, +{-0.328308, -0.763591, 0.556007}, +{0.83143, -0.536791, -0.143455}, +{0.441958, -0.730231, 0.520995}, +{-0.743334, -0.528105, -0.410559}, +{0.224172, -0.0985229, 0.969557}, +{-0.637509, 0.507607, -0.579584}, +{0.469148, 0.519398, 0.714232}, +{-0.319226, -0.877222, 0.358575}, +{0.898065, 0.417732, 0.137763}, +{-0.099507, -0.497502, 0.861737}, +{0.159573, -0.561332, -0.812061}, +{-0.52404, 0.0364752, 0.850912}, +{0.758107, 0.207642, 0.61819}, +{0.670427, -0.496657, -0.551235}, +{0.566635, -0.753554, 0.333289}, +{-0.771703, 0.595955, -0.222064}, +{-0.765602, 0.643141, -0.0149266}, +{-0.468694, 0.880902, -0.0658571}, +{0.702889, 0.707626, -0.0721965}, +{0.622179, -0.692399, 0.365345}, +{0.598067, -0.528263, 0.602705}, +{-0.226636, -0.938561, -0.260267}, +{0.786909, 0.261643, 0.558854}, +{0.872882, -0.196339, -0.446686}, +{0.0101144, 0.00820103, 0.999915}, +{-0.422104, 0.439229, 0.793036}, +{0.877469, -0.353106, -0.324598}, +{0.314481, 0.929084, 0.194691}, +{0.915907, -0.186664, 0.355347}, +{0.91686, 0.291534, -0.27272}, +{0.609159, 0.793034, -0.00473143}, +{-0.354846, 0.448743, 0.820191}, +{-0.858883, 0.49835, 0.118182}, +{0.495453, 0.662732, -0.561526}, +{0.673831, 0.570017, 0.470141}, +{-0.203014, 0.901112, -0.383123}, +{0.779737, 0.495931, -0.382181}, +{-0.476616, -0.557405, -0.679806}, +{0.885925, 0.424543, 0.186815}, +{0.181586, 0.439014, -0.87994}, +{-0.181881, 0.0754753, -0.98042}, +{0.862415, 0.38164, -0.332552}, +{-0.778778, -0.510925, 0.363951}, +{0.603291, -0.0925035, -0.792138}, +{0.288124, -0.87777, 0.38276}, +{0.790762, 0.27846, -0.54512}, +{-0.011042, -0.408191, -0.91283}, +{-0.294222, -0.698657, 0.652159}, +{0.405006, 0.43625, 0.803528}, +{-0.647582, -0.736003, 0.197325}, +{0.215671, -0.967785, -0.129914}, +{0.712878, 0.566434, 0.41347}, +{-0.0152908, -0.641976, 0.766572}, +{-0.652819, 0.669242, 0.354883}, +{-0.246104, 0.725451, -0.64277}, +{-0.520585, 0.59341, -0.613886}, +{0.554281, 0.747574, 0.365931}, +{-0.688654, -0.35872, -0.630139}, +{-0.25507, -0.836907, 0.48428}, +{0.486616, 0.727913, 0.48306}, +{-0.843267, -0.260083, -0.47038}, +{0.545478, 0.348476, -0.762246}, +{0.569854, 0.818421, -0.0738432}, +{0.750315, -0.196551, 0.631185}, +{-0.828083, 0.264037, -0.494533}, +{0.942376, 0.309682, 0.126585}, +{-0.576841, 0.214375, 0.788224}, +{0.754839, -0.169471, -0.633639}, +{0.630189, 0.15654, 0.760498}, +{-0.734806, 0.301914, 0.607379}, +{-0.569296, -0.742515, 0.352949}, +{0.675027, -0.51459, 0.528711}, +{-0.375712, 0.886361, 0.270564}, +{0.37575, 0.0813247, -0.923146}, +{-0.879208, -0.0780131, -0.470008}, +{0.741017, 0.282478, -0.60918}, +{-0.240742, 0.364346, -0.899608}, +{-0.996839, -0.064233, -0.0467549}, +{-0.0660192, 0.790336, -0.609106}, +{-0.868124, 0.495013, -0.0363628}, +{0.286388, 0.642647, -0.710624}, +{-0.474909, 0.240735, 0.846468}, +{0.0830907, -0.353883, 0.931592}, +{-0.625044, -0.473844, 0.620316}, +{0.369605, -0.715779, -0.592496}, +{0.818353, -0.0148031, -0.574525}, +{-0.638211, -0.103029, 0.762936}, +{0.255755, 0.964584, -0.0645492}, +{-0.867772, -0.107597, 0.485174}, +{-0.928284, -0.0284341, -0.370784}, +{-0.904595, 0.414683, 0.098718}, +{-0.944068, 0.174639, -0.279709}, +{-0.942562, -0.229695, -0.242522}, +{0.797428, 0.53142, 0.285835}, +{-0.388883, 0.918703, -0.0689605}, +{-0.73654, -0.0136207, -0.676256}, +{-0.401609, 0.76809, 0.498747}, +{-0.0151886, -0.899136, -0.437405}, +{0.287461, 0.896706, -0.336576}, +{0.267065, 0.291049, -0.918676}, +{0.00482882, -0.771766, -0.635888}, +{0.736981, -0.58476, -0.338991}, +{-0.44478, 0.0614279, 0.893531}, +{-0.184751, -0.660227, 0.727989}, +{-0.858813, -0.202172, 0.470709}, +{-0.957232, -0.00457735, -0.289285}, +{-0.620435, 0.301839, 0.723846}, +{-0.751373, -0.659871, 0.00301572}, +{-0.876374, -0.430388, -0.21618}, +{0.13295, -0.70929, 0.692266}, +{-0.95534, 0.123508, 0.26846}, +{0.903755, -0.421953, -0.0719832}, +{0.0307017, -0.776278, -0.629643}, +{0.830653, 0.532647, 0.162182}, +{0.1492, 0.988644, -0.0179815}, +{-0.261667, -0.441361, -0.85833}, +{0.651251, 0.54323, 0.52988}, +{0.584475, -0.0452954, -0.810146}, +{0.696754, 0.0371246, 0.716349}, +{0.522869, -0.081917, -0.848468}, +{-0.509665, -0.845898, 0.157159}, +{0.319676, -0.531589, 0.78436}, +{-0.725095, -0.436541, 0.532607}, +{-0.543242, -0.550909, -0.633552}, +{-0.92538, 0.16129, 0.343012}, +{-0.698421, -0.088602, 0.710181}, +{0.53057, 0.50733, -0.679053}, +{-0.264778, -0.415942, -0.869991}, +{0.480752, -0.169424, -0.860333}, +{0.280266, -0.948345, 0.148634}, +{-0.788316, 0.612687, 0.0563245}, +{-0.424913, 0.412778, 0.805645}, +{0.735899, 0.638879, -0.224246}, +{0.304264, 0.152315, -0.940332}, +{-0.105177, -0.346012, 0.932316}, +{-0.0933694, -0.981366, -0.167937}, +{-0.961452, 0.22582, -0.156894}, +{0.741651, 0.1078, 0.662067}, +{0.0272823, 0.96704, -0.253158}, +{-0.837409, -0.513202, -0.188067}, +{0.65311, -0.756904, 0.0233167}, +{0.130552, 0.987629, -0.0868651}, +{-0.548392, 0.700938, 0.456019}, +{-0.570945, -0.549975, 0.609549}, +{0.927601, -0.222953, -0.299747}, +{0.631683, -0.749062, 0.199709}, +{0.794033, -0.565579, 0.222784}, +{-0.985424, 0.16971, -0.0117232}, +{-0.987125, 0.158414, 0.0221294}, +{0.6945, -0.237093, -0.679305}, +{0.937707, 0.0419465, 0.344885}, +{-0.794767, -0.273678, -0.541706}, +{0.310837, -0.826207, 0.469854}, +{-0.0436197, -0.253575, 0.966332}, +{-0.643576, 0.762783, 0.0630263}, +{-0.602649, -0.79599, -0.0566936}, +{-0.720945, -0.665885, 0.191924}, +{-0.49762, 0.709801, -0.498555}, +{-0.779078, 0.230535, -0.583002}, +{-0.100661, -0.757263, 0.645307}, +{-0.178135, -0.754622, 0.631517}, +{-0.234843, 0.891708, 0.386918}, +{0.111796, -0.761882, -0.637994}, +{0.111543, -0.591351, -0.798663}, +{-0.913901, -0.281545, 0.292434}, +{0.0032347, -0.938902, 0.344168}, +{0.648865, 0.754799, -0.0961935}, +{0.362354, 0.146616, -0.920437}, +{0.594847, -0.802784, 0.041174}, +{0.685815, -0.700193, 0.198463}, +{-0.274087, 0.920141, 0.279673}, +{0.986729, -0.0918803, -0.133883}, +{-0.663129, 0.562876, 0.493388}, +{0.690926, -0.675357, 0.257906}, +{-0.211755, -0.951508, 0.223142}, +{0.129467, -0.727634, -0.673637}, +{0.14455, -0.553306, 0.82034}, +{0.685535, 0.514939, -0.514664}, +{0.0194242, 0.664541, 0.746999}, +{0.972224, -0.162932, -0.168031}, +{0.889432, -0.273187, 0.366443}, +{0.473777, 0.879829, -0.0378938}, +{-0.683078, 0.0586597, -0.727986}, +{-0.95912, 0.0867863, 0.269365}, +{0.532463, 0.465156, 0.707187}, +{0.718662, -0.280869, -0.636111}, +{-0.552725, 0.591829, 0.586715}, +{-0.0558193, -0.351468, -0.934534}, +{0.559229, -0.685984, 0.465499}, +{0.0948403, 0.424455, 0.900468}, +{0.757652, -0.223044, 0.613363}, +{-0.920629, -0.169817, 0.351575}, +{0.0710956, -0.994631, -0.0752017}, +{-0.455387, 0.626499, -0.632552}, +{0.322906, -0.0925992, 0.94189}, +{-0.974208, 0.182864, 0.132209}, +{0.350204, 0.712835, -0.607638}, +{0.334015, 0.884989, -0.324389}, +{0.358948, -0.876681, -0.320291}, +{0.657462, 0.205966, -0.724791}, +{-0.706019, -0.0659589, 0.705114}, +{0.859072, -0.3472, -0.376095}, +{0.940719, 0.283011, -0.186958}, +{-0.85364, -0.224256, -0.470115}, +{0.949426, 0.220744, 0.2233}, +{0.783962, -0.617441, -0.0645775}, +{-0.975956, 0.21251, 0.0484636}, +{0.775438, 0.631366, 0.00849196}, +{0.39182, -0.0622683, -0.917932}, +{-0.059789, 0.118599, 0.99114}, +{-0.360291, -0.245463, -0.899965}, +{-0.601481, 0.676923, 0.424259}, +{-0.20259, -0.913009, -0.354078}, +{0.459564, -0.588781, 0.664934}, +{0.230215, 0.493284, -0.838852}, +{-0.343635, -0.735311, -0.584151}, +{-0.409754, 0.163919, -0.897347}, +{0.126217, -0.821199, -0.556508}, +{-0.247265, 0.936415, -0.248972}, +{-0.316676, -0.789865, -0.525194}, +{-0.107463, 0.657995, 0.745315}, +{-0.355742, 0.564518, 0.744826}, +{-0.543011, 0.828142, -0.138994}, +{0.12793, -0.879784, 0.457837}, +{-0.991698, -0.0942452, -0.0874776}, +{-0.425363, -0.632764, -0.647051}, +{0.213576, 0.562641, -0.798637}, +{0.99898, -0.00806954, 0.0444214}, +{-0.504464, -0.193335, 0.841509}, +{0.0591607, -0.0125521, -0.99817}, +{0.865485, -0.158699, -0.475132}, +{0.119332, -0.988565, -0.09219}, +{-0.477049, -0.208453, 0.853798}, +{-0.418463, -0.902417, -0.102627}, +{-0.891838, 0.452298, 0.00724576}, +{0.616866, 0.413522, -0.669684}, +{0.634868, -0.372626, 0.676825}, +{0.690414, 0.663074, 0.289243}, +{-0.444446, 0.170766, -0.879378}, +{-0.67984, -0.143921, 0.719099}, +{-0.163758, -0.236728, -0.957676}, +{0.704144, -0.118334, -0.700127}, +{0.736601, -0.672842, 0.0685784}, +{0.0709733, 0.182407, -0.980658}, +{-0.120307, -0.13727, -0.9832}, +{-0.639445, 0.759403, 0.120072}, +{-0.525032, 0.805427, 0.275008}, +{0.891131, -0.424638, 0.1599}, +{-0.84899, 0.523378, -0.0727353}, +{-0.824555, -0.565611, 0.0139102}, +{-0.850265, 0.0181804, -0.52604}, +{0.256361, 0.564212, 0.784821}, +{-0.800344, 0.11303, -0.588789}, +{0.0234081, 0.987169, -0.157953}, +{-0.121714, -0.248635, 0.96092}, +{-0.178905, 0.889316, 0.420844}, +{-0.358664, -0.504496, -0.785394}, +{-0.470362, -0.0791909, 0.878913}, +{0.30349, 0.841588, -0.446792}, +{0.933447, -0.288374, 0.213349}, +{0.816841, -0.405678, -0.410117}, +{0.830222, 0.551839, 0.0787719}, +{-0.613068, -0.782653, 0.107713}, +{0.865561, -0.444805, -0.230114}, +{-0.662471, -0.60741, 0.43839}, +{0.0326518, -0.877506, -0.478453}, +{0.464731, 0.643192, 0.608547}, +{-0.944889, 0.224951, -0.237869}, +{-0.302373, 0.0874119, 0.949173}, +{-0.0462551, -0.1713, 0.984133}, +{0.358364, -0.208635, -0.90997}, +{-0.157454, 0.0409117, -0.986678}, +{0.249773, -0.0524162, 0.966885}, +{0.321835, -0.859658, -0.396749}, +{0.4327, -0.694337, -0.575036}, +{-0.569399, 0.819209, 0.0684162}, +{0.0904767, -0.238756, -0.966855}, +{0.869929, 0.382537, -0.31127}, +{0.733488, 0.505524, 0.454358}, +{-0.855304, -0.396773, -0.333207}, +{0.45836, 0.136808, -0.878174}, +{0.298194, -0.909608, 0.289297}, +{-0.988645, -0.148851, 0.0206243}, +{-0.117944, -0.983034, 0.140478}, +{0.602534, -0.664813, 0.441562}, +{0.671508, 0.526886, -0.521026}, +{-0.167556, -0.778759, 0.604533}, +{0.455937, 0.169028, -0.873814}, +{0.991583, 0.115411, -0.0586829}, +{-0.00861939, -0.547692, -0.836636}, +{0.812841, 0.545421, -0.204465}, +{-0.908553, 0.259581, 0.327335}, +{-0.433997, -0.307657, 0.846755}, +{0.919116, 0.0399101, -0.391959}, +{-0.725259, 0.413168, -0.550719}, +{-0.705231, -0.597492, 0.381644}, +{-0.219463, -0.0440314, -0.974627}, +{0.607282, -0.431423, -0.667145}, +{-0.174042, -0.0374028, -0.984028}, +{0.66356, -0.454256, 0.594424}, +{-0.484671, 0.71054, 0.510125}, +{0.93613, 0.10453, 0.335759}, +{0.671025, 0.374079, -0.640148}, +{0.54611, -0.712642, 0.440348}, +{0.207995, -0.791596, -0.574556}, +{0.835454, 0.364755, -0.41106}, +{-0.206201, 0.416886, -0.885261}, +{0.34985, 0.617049, 0.70488}, +{0.384801, 0.0535241, -0.921446}, +{0.262527, -0.483425, 0.835093}, +{-0.733786, -0.479209, -0.481577}, +{0.326935, 0.810971, 0.485222}, +{0.966934, 0.0404131, -0.251805}, +{-0.310243, 0.184171, -0.932647}, +{0.139967, -0.679728, -0.719985}, +{0.443626, -0.816249, 0.370045}, +{-0.359235, 0.0334447, -0.932648}, +{0.169891, -0.49263, 0.853494}, +{0.578096, 0.26258, 0.772565}, +{0.0537304, 0.521124, 0.851788}, +{-0.717517, 0.451477, 0.530413}, +{0.763117, -0.470906, 0.442607}, +{0.91703, 0.0926498, 0.387907}, +{0.316176, -0.772983, -0.550027}, +{-0.90843, -0.052426, -0.414737}, +{-0.347493, -0.58359, 0.733942}, +{-0.0626683, -0.882924, -0.465315}, +{-0.252387, -0.548609, -0.797075}, +{0.714173, -0.0914793, 0.693965}, +{0.233782, -0.767252, 0.597219}, +{-0.314474, -0.925876, 0.209426}, +{0.931926, -0.10596, 0.346824}, +{-0.778225, 0.627037, -0.0344964}, +{0.275732, -0.325215, 0.904548}, +{0.559885, -0.320851, 0.763927}, +{0.901886, 0.425669, 0.073535}, +{0.176656, -0.977845, 0.112304}, +{-0.653968, -0.755363, -0.0418683}, +{-0.429666, 0.876905, -0.215466}, +{-0.542134, -0.40844, -0.734348}, +{-0.823489, -0.168363, -0.541775}, +{-0.729667, -0.0944553, 0.677248}, +{-0.548068, 0.830967, -0.0954776}, +{0.297922, 0.881448, 0.366457}, +{0.788219, -0.0982938, -0.607494}, +{-0.0417261, 0.908809, 0.41512}, +{0.000459814, -0.57457, -0.818456}, +{0.538454, -0.755001, -0.374221}, +{0.19097, 0.0742313, -0.978785}, +{-0.147496, -0.106212, -0.983343}, +{0.678169, 0.444063, -0.585573}, +{0.133644, -0.373224, -0.918065}, +{0.585554, 0.502003, -0.636491}, +{-0.870876, -0.384724, -0.30588}, +{-0.153442, 0.800151, 0.57984}, +{0.0315088, -0.929841, -0.366609}, +{0.8231, -0.527345, -0.210746}, +{-0.309131, 0.730474, -0.608971}, +{0.651242, 0.758544, -0.0222261}, +{-0.600754, -0.125364, 0.789543}, +{-0.814695, 0.533416, 0.227463}, +{0.0515687, -0.719852, 0.692209}, +{-0.283638, -0.205937, 0.936557}, +{0.815047, 0.10128, -0.570474}, +{0.191576, -0.52085, -0.831873}, +{-0.87927, -0.472758, 0.0581659}, +{0.400315, -0.85878, 0.319758}, +{0.531489, 0.666551, -0.522714}, +{-0.503905, -0.599228, -0.622098}, +{-0.979557, -0.175644, 0.098065}, +{0.594485, 0.734085, -0.328187}, +{-0.241374, 0.838442, -0.488625}, +{0.183644, 0.955114, 0.232449}, +{0.274368, 0.214868, -0.937312}, +{0.499199, -0.780444, 0.37644}, +{-0.79078, 0.518353, 0.325542}, +{-0.216024, 0.970045, -0.111113}, +{0.819198, 0.572818, -0.028193}, +{0.243772, -0.961188, 0.129201}, +{-0.424141, 0.00116128, -0.905595}, +{0.209246, 0.0705106, -0.975318}, +{-0.17841, 0.0112228, 0.983892}, +{-0.640541, 0.696178, 0.324104}, +{0.793991, 0.570645, 0.209625}, +{-0.677619, -0.0775114, -0.731317}, +{-0.302385, 0.00218443, -0.953183}, +{0.624764, -0.769869, -0.130275}, +{0.879075, 0.0869285, -0.46869}, +{-0.975673, 0.01647, -0.218613}, +{0.712043, 0.263357, -0.650875}, +{0.0300125, 0.973625, -0.226174}, +{0.192627, -0.828075, 0.526485}, +{-0.839596, -0.460247, -0.288533}, +{-0.6383, 0.189992, -0.745973}, +{0.845088, 0.137383, 0.516674}, +{0.653089, 0.718293, 0.239853}, +{0.155554, -0.708725, -0.688122}, +{-0.264104, -0.491577, 0.82982}, +{0.240888, -0.930408, 0.276251}, +{-0.983133, -0.00417462, 0.182844}, +{0.821787, -0.56598, -0.0658178}, +{0.489345, -0.814524, -0.311596}, +{-0.389881, -0.87237, 0.294896}, +{-0.919132, -0.138291, 0.368879}, +{0.972876, -0.0365673, -0.22842}, +{0.175514, 0.253549, -0.951266}, +{-0.0238983, -0.503645, 0.86358}, +{0.0398349, 0.00378197, -0.999199}, +{-0.51868, -0.345269, 0.782151}, +{-0.501547, -0.210732, -0.839073}, +{-0.0776411, -0.940979, 0.32944}, +{0.956986, 0.270351, -0.105299}, +{-0.56079, 0.364754, 0.743283}, +{-0.940409, 0.318648, -0.118722}, +{-0.150701, -0.436316, 0.887084}, +{0.915301, 0.402326, -0.0189012}, +{0.474153, 0.664444, -0.577662}, +{-0.0937675, -0.739393, -0.666713}, +{0.950082, 0.311892, -0.00818979}, +{0.949767, 0.0653237, -0.306063}, +{-0.530291, -0.323287, -0.783758}, +{-0.076617, -0.826303, 0.557991}, +{-0.0351631, 0.862663, 0.504555}, +{0.756335, 0.266598, -0.597396}, +{-0.620935, 0.00582877, 0.783841}, +{0.99931, -0.00239347, -0.0370586}, +{-0.621095, -0.288003, -0.7289}, +{0.764099, 0.319585, -0.560373}, +{-0.257949, -0.389364, 0.884227}, +{0.346314, -0.596919, -0.723709}, +{0.647905, 0.629267, -0.429235}, +{0.656249, 0.726568, 0.203561}, +{-0.980253, -0.114104, -0.16151}, +{0.164778, -0.47099, 0.866612}, +{0.977933, -0.0602176, 0.200053}, +{0.191464, 0.3648, -0.911187}, +{-0.49252, 0.704265, -0.511307}, +{-0.850865, -0.508114, -0.133598}, +{0.246271, -0.668264, 0.701979}, +{-0.763972, -0.359764, 0.535645}, +{-0.435154, 0.50026, 0.748586}, +{0.985818, 0.0265021, 0.165712}, +{0.870094, 0.303012, -0.388741}, +{-0.251646, -0.596859, 0.761862}, +{-0.0314153, 0.972191, -0.232074}, +{-0.635522, -0.295725, 0.713203}, +{-0.284228, -0.924444, 0.254199}, +{0.256031, -0.147252, -0.955387}, +{0.243343, -0.512303, -0.823608}, +{-0.677562, -0.711408, -0.186569}, +{-0.91637, 0.191876, 0.351355}, +{0.599753, 0.433165, -0.672804}, +{0.750807, -0.33431, -0.569672}, +{-0.778404, 0.436582, 0.451091}, +{-0.106975, 0.0191631, -0.994077}, +{-0.527226, -0.0978258, -0.844075}, +{-0.210132, -0.592649, -0.777568}, +{0.833936, -0.339995, -0.434689}, +{0.529203, -0.544977, 0.650341}, +{0.466094, -0.31717, 0.82593}, +{0.598173, -0.242013, 0.763949}, +{0.261827, 0.3735, -0.889913}, +{0.539218, 0.44177, -0.716996}, +{0.562764, -0.581429, -0.587569}, +{0.332937, -0.837738, 0.432837}, +{-0.466274, 0.820268, 0.331283}, +{0.603179, -0.439103, 0.665856}, +{-0.627887, 0.507965, 0.589685}, +{0.0601872, 0.909071, 0.412271}, +{-0.955793, 0.0497691, -0.289799}, +{0.587744, -0.693149, -0.417254}, +{-0.0415099, -0.72393, -0.688624}, +{0.287112, -0.212077, 0.934125}, +{-0.0483611, 0.460892, 0.886138}, +{-0.148704, 0.965334, -0.214515}, +{-0.874474, -0.469137, 0.123309}, +{-0.990191, -0.126203, 0.059953}, +{0.817607, -0.322577, 0.476931}, +{0.274553, 0.958455, -0.0773603}, +{-0.134682, 0.688232, 0.71288}, +{-0.773644, 0.623428, -0.11319}, +{-5.89496e-05, 0.741657, 0.670779}, +{0.394616, -0.328739, -0.858027}, +{0.786066, 0.46631, 0.405777}, +{-0.655556, -0.359965, 0.663831}, +{0.171929, 0.659698, -0.7316}, +{0.299308, 0.0269345, -0.953776}, +{0.998258, 0.0209519, 0.0551481}, +{0.936859, 0.249259, 0.245286}, +{0.765375, -0.596536, 0.241548}, +{-0.0613229, 0.824879, -0.561974}, +{-0.645998, -0.464598, -0.60567}, +{-0.0972915, 0.873121, 0.477696}, +{0.885497, 0.353956, 0.301016}, +{-0.145704, 0.93337, 0.328009}, +{0.385144, -0.466525, -0.796253}, +{0.659568, 0.269859, 0.701531}, +{-0.694602, 0.281866, 0.661876}, +{0.668549, -0.262047, -0.695969}, +{-0.620818, -0.457639, 0.636515}, +{0.511494, 0.350507, 0.78455}, +{0.461604, -0.640301, -0.613952}, +{0.375621, 0.409852, 0.831222}, +{0.820899, -0.139756, -0.553709}, +{0.019905, 0.99799, -0.0601682}, +{-0.709974, -0.180407, -0.680728}, +{0.740996, -0.649002, -0.1724}, +{0.69036, 0.000954807, -0.723465}, +{0.404682, -0.879801, 0.249363}, +{0.748239, -0.0815755, 0.658395}, +{0.671325, -0.0221961, 0.74083}, +{0.124601, 0.770172, -0.625547}, +{-0.118001, 0.339902, -0.933029}, +{0.168877, -0.719702, 0.673431}, +{0.554339, -0.706647, -0.439726}, +{-0.791062, 0.585748, 0.176409}, +{0.318047, -0.38521, 0.866291}, +{0.431697, 0.0799711, 0.898467}, +{0.369864, -0.568824, -0.734602}, +{0.215689, 0.938682, 0.268989}, +{-0.352946, 0.81691, 0.456167}, +{0.60321, -0.448085, 0.659816}, +{-0.425225, 0.904675, -0.0273187}, +{-0.654337, -0.413613, -0.633063}, +{0.0782959, 0.624162, -0.777362}, +{-0.418223, -0.588036, 0.692317}, +{-0.937826, -0.34017, -0.0690427}, +{-0.204592, 0.0089079, -0.978807}, +{0.473523, 0.0721667, 0.87782}, +{-0.645711, 0.544814, 0.53501}, +{0.383744, 0.854524, -0.350043}, +{0.366792, 0.805391, 0.465627}, +{0.838413, -0.543225, 0.0443802}, +{-0.733075, 0.672997, 0.0983661}, +{-0.514796, 0.158357, 0.842561}, +{-0.72453, 0.575178, 0.379772}, +{0.411343, -0.18143, 0.893241}, +{0.173458, 0.604936, 0.777152}, +{-0.920168, 0.376051, -0.108981}, +{0.105474, -0.993208, 0.0491193}, +{0.53469, -0.694905, 0.480847}, +{-0.231997, -0.962862, -0.138106}, +{-0.821562, 0.504169, 0.266178}, +{0.421044, -0.656596, 0.625783}, +{0.843756, -0.53382, -0.0557783}, +{-0.433606, 0.850786, -0.296899}, +{0.00372182, -0.642433, -0.766333}, +{0.412864, -0.806905, 0.422431}, +{0.123034, -0.48801, 0.864123}, +{-0.443562, -0.446588, 0.777053}, +{-0.819389, 0.209904, -0.533425}, +{0.111726, 0.156798, -0.981291}, +{0.214009, -0.712208, -0.66855}, +{0.149246, -0.464184, -0.873074}, +{0.60412, -0.638835, -0.476372}, +{-0.747845, -0.601204, 0.28157}, +{-0.65969, -0.343959, -0.668208}, +{-0.722156, -0.598036, -0.347625}, +{-0.672702, -0.624359, 0.397049}, +{-0.658922, -0.54558, -0.517846}, +{-0.826152, 0.465378, -0.317641}, +{-0.282905, 0.210378, 0.935792}, +{-0.156639, 0.850159, 0.502687}, +{0.355161, -0.934804, -0.00171247}, +{-0.502822, 0.550751, 0.666215}, +{0.916949, -0.388307, -0.091773}, +{-0.99333, 0.0671763, -0.0937125}, +{-0.457716, 0.677717, 0.575495}, +{-0.173418, 0.886306, -0.429405}, +{-0.344385, -0.0278936, -0.938414}, +{-0.262297, 0.0436831, -0.963998}, +{0.987115, -0.159831, 0.0075656}, +{-0.180186, 0.446505, 0.876451}, +{0.313629, 0.72764, 0.610063}, +{-0.0423349, -0.109252, -0.993112}, +{-0.95274, 0.261592, 0.154457}, +{0.925288, -0.0292937, -0.378131}, +{0.474369, -0.868437, 0.144194}, +{0.689195, 0.160826, -0.706502}, +{0.125528, 0.110244, 0.985946}, +{0.0231181, -0.945445, -0.324962}, +{0.20662, 0.815494, 0.540628}, +{-0.219843, -0.896669, -0.384258}, +{-0.815297, 0.458069, 0.354209}, +{0.168339, 0.421492, -0.891071}, +{0.585666, 0.202557, 0.784835}, +{0.310514, -0.638222, 0.704452}, +{-0.812265, -0.222522, 0.539176}, +{-0.495839, 0.561038, -0.662857}, +{-0.301373, 0.908805, -0.288526}, +{0.754017, 0.646932, 0.113746}, +{-0.508164, -0.751853, -0.420103}, +{0.726083, 0.645145, 0.237891}, +{0.28512, 0.731719, -0.619107}, +{0.0794979, 0.984845, -0.154147}, +{0.0556937, 0.605824, 0.793647}, +{-0.233296, -0.968743, -0.0843156}, +{0.333942, 0.490241, -0.805075}, +{-0.079861, -0.851614, 0.51805}, +{-0.970856, 0.227645, -0.0749425}, +{-0.459637, 0.457689, 0.761088}, +{-0.978842, 0.190446, -0.0748231}, +{0.650879, 0.33597, -0.680794}, +{0.801608, -0.59763, -0.0162114}, +{-0.629888, 0.389155, 0.672161}, +{-0.856618, 0.450623, 0.251285}, +{0.171063, -0.938621, -0.299548}, +{-0.567498, 0.590647, 0.573657}, +{0.266643, 0.769541, -0.580265}, +{-0.251205, -0.836471, -0.487044}, +{-0.306067, 0.52277, 0.795635}, +{0.430566, -0.696437, -0.574098}, +{-0.425686, 0.148607, 0.892585}, +{-0.467647, -0.883912, 0.00228919}, +{-0.302421, -0.840909, -0.448792}, +{0.464827, 0.404151, 0.787781}, +{-0.857466, -0.183054, -0.480878}, +{0.915171, -0.190973, -0.354953}, +{0.158798, -0.566308, 0.808751}, +{0.443843, -0.884444, -0.144092}, +{0.346685, -0.455329, -0.820052}, +{0.272133, 0.626757, 0.73015}, +{-0.872292, 0.16362, -0.460799}, +{-0.984147, -0.176851, -0.0133781}, +{-0.891848, -0.215392, 0.397761}, +{0.264707, 0.205057, 0.942275}, +{0.64309, -0.679922, -0.352337}, +{0.812084, 0.0371636, 0.582356}, +{0.701293, -0.663248, -0.261325}, +{-0.338258, 0.844877, 0.414445}, +{-0.674509, 0.369935, -0.638893}, +{-0.197091, -0.968383, 0.152937}, +{-0.667405, 0.638121, -0.383891}, +{0.233795, -0.398034, 0.887079}, +{-0.939548, -0.301691, 0.16196}, +{0.160858, -0.237079, -0.958081}, +{0.0845287, 0.8662, -0.492496}, +{-0.0435361, -0.998979, -0.0120507}, +{0.989118, -0.109914, 0.0977977}, +{-0.127403, -0.75228, 0.646408}, +{0.234517, 0.930542, -0.281234}, +{-0.999808, -0.0128499, -0.014774}, +{-0.836485, 0.220187, -0.501808}, +{-0.50873, 0.158151, 0.846275}, +{-0.944849, 0.268477, 0.187567}, +{0.930731, -0.292601, -0.21937}, +{-0.414724, 0.339664, 0.844176}, +{0.373422, -0.633236, -0.677915}, +{-0.323997, -0.811983, -0.485499}, +{-0.0390545, 0.50504, 0.862212}, +{0.539234, 0.776146, 0.32684}, +{-0.704434, -0.217529, 0.675614}, +{-0.368794, 0.346818, 0.862385}, +{0.731713, -0.553445, 0.397863}, +{0.677381, 0.662923, -0.318886}, +{0.301013, -0.202372, -0.9319}, +{-0.212346, -0.230559, 0.949606}, +{0.213772, -0.503309, 0.837247}, +{-0.591128, -0.244878, -0.768507}, +{0.309146, -0.881582, -0.35671}, +{-0.620206, -0.55891, 0.550422}, +{0.451951, -0.889024, 0.0733245}, +{-0.579982, -0.490442, 0.650452}, +{0.148781, 0.705572, 0.692844}, +{-0.334492, 0.76468, 0.550799}, +{0.756816, 0.153657, 0.63531}, +{0.0222091, -0.89284, 0.449826}, +{0.090129, 0.828677, 0.552423}, +{0.238396, -0.95839, -0.15702}, +{0.753027, 0.345981, 0.559685}, +{0.684964, 0.557478, -0.469088}, +{0.692084, -0.658389, -0.295879}, +{-0.351483, -0.255571, 0.900635}, +{-0.0671527, 0.997507, 0.0216648}, +{-0.393364, -0.899929, 0.188129}, +{0.406961, 0.837373, -0.36495}, +{0.708903, 0.00497088, -0.705289}, +{-0.50015, 0.505166, -0.703319}, +{-0.51242, -0.618309, 0.595919}, +{0.950543, 0.143783, 0.275308}, +{0.983846, 0.074267, -0.162887}, +{-0.56831, 0.570832, 0.5926}, +{0.234092, -0.863076, -0.447551}, +{-0.381405, 0.27316, 0.883127}, +{0.95169, -0.297611, 0.0755842}, +{-0.712931, 0.65196, -0.25822}, +{-0.899104, 0.0366451, -0.436198}, +{0.911486, -0.11193, 0.39581}, +{-0.0022299, 0.814253, 0.580506}, +{-0.839844, 0.50398, -0.201658}, +{0.444181, 0.49455, 0.747077}, +{0.83608, -0.396375, -0.379285}, +{0.134471, 0.880986, 0.453631}, +{0.213357, 0.644758, -0.734006}, +{0.426643, 0.654371, -0.624319}, +{-0.635237, 0.348319, 0.68931}, +{-0.408673, 0.6227, 0.667256}, +{0.447635, 0.182926, 0.875306}, +{-0.510009, -0.259357, -0.820137}, +{-0.368033, 0.68987, 0.623404}, +{-0.527456, -0.777019, 0.343556}, +{-0.926919, 0.146508, -0.34548}, +{0.261801, -0.90481, 0.335827}, +{0.773176, -0.634163, 0.00604129}, +{0.155679, -0.14153, -0.977616}, +{0.332736, 0.512715, 0.791461}, +{0.495279, 0.781618, -0.379173}, +{0.625207, 0.777772, 0.0647083}, +{-0.236341, 0.969085, -0.0708258}, +{0.216341, 0.48675, 0.846328}, +{0.561434, -0.82746, -0.0101344}, +{-0.452723, 0.565684, -0.689234}, +{0.513916, 0.750113, -0.416198}, +{0.0347479, -0.0683969, -0.997053}, +{-0.944211, -0.188021, 0.270394}, +{0.886875, -0.460895, 0.0320881}, +{-0.584536, -0.724451, -0.365362}, +{0.628258, -0.66215, -0.408472}, +{0.76081, 0.379045, 0.526776}, +{-0.153342, -0.670396, -0.725986}, +{-0.0114366, -0.565435, 0.824713}, +{-0.938396, 0.331407, 0.0978935}, +{0.795728, -0.596888, 0.102671}, +{0.838039, -0.372632, 0.398542}, +{-0.561378, -0.764354, -0.317203}, +{0.676486, 0.479759, 0.558747}, +{0.557947, 0.689209, -0.462261}, +{-0.265924, 0.593949, 0.759282}, +{-0.799428, -0.581931, 0.149237}, +{0.45893, -0.305148, 0.834427}, +{0.639561, -0.638439, 0.428201}, +{0.904679, -0.138703, -0.402886}, +{-0.938455, 0.0643892, -0.339346}, +{-0.683107, -0.646823, 0.339095}, +{0.203632, 0.920944, -0.332259}, +{-0.240509, -0.819002, 0.520952}, +{-0.821371, -0.504624, -0.265902}, +{0.336362, -0.678416, 0.653156}, +{0.955741, -0.276059, -0.101739}, +{0.196924, -0.00279708, -0.980415}, +{0.39665, 0.741477, -0.541185}, +{-0.449283, -0.0169401, 0.893229}, +{0.863886, 0.478711, -0.15664}, +{-0.0357932, 0.351303, -0.935577}, +{-0.125839, -0.948808, 0.289702}, +{-0.146262, -0.989221, 0.00705224}, +{0.79852, -0.536935, -0.272153}, +{-0.356328, 0.927727, -0.111145}, +{-0.906019, -0.250389, 0.341227}, +{-0.803486, -0.0525895, 0.592996}, +{0.901219, 0.260754, 0.346137}, +{-0.243058, -0.503953, -0.828827}, +{-0.198521, -0.501043, 0.842345}, +{-0.796423, 0.56039, -0.227318}, +{-0.21248, 0.925426, 0.313751}, +{-0.0390276, 0.539506, -0.841077}, +{-0.737398, -0.631663, 0.239263}, +{-0.597631, 0.77358, -0.21074}, +{0.740756, 0.662556, -0.110905}, +{0.578925, 0.527461, 0.621796}, +{0.0363777, -0.999164, -0.018655}, +{-0.460731, -0.143626, -0.875841}, +{-0.925166, 0.378241, 0.0316327}, +{-0.419056, -0.768592, -0.483383}, +{0.26347, -0.221034, 0.939003}, +{-0.544266, -0.574771, 0.611075}, +{-0.746663, -0.427935, -0.50928}, +{-0.0619359, 0.26529, 0.962177}, +{0.021649, -0.919931, -0.391482}, +{-0.87139, 0.0706917, -0.485471}, +{0.258661, -0.716464, -0.6479}, +{0.0288779, -0.901951, 0.430871}, +{-0.41798, -0.827503, 0.374875}, +{0.442131, -0.276525, -0.853261}, +{0.149144, 0.184364, 0.971476}, +{0.750061, -0.552953, -0.362838}, +{0.711364, 0.645471, -0.278079}, +{-0.693798, 0.704041, -0.151558}, +{0.233167, -0.822244, 0.51918}, +{-0.209169, 0.0646803, 0.975738}, +{0.750489, -0.483675, 0.450362}, +{-0.674274, 0.71865, -0.169994}, +{-0.553937, -0.825238, 0.110163}, +{-0.119726, 0.691, -0.712871}, +{-0.82293, -0.547466, -0.151877}, +{0.78167, -0.530225, 0.328411}, +{-0.822027, -0.561008, -0.0976778}, +{0.974516, 0.220653, -0.040392}, +{-0.92558, -0.043001, -0.376102}, +{0.849315, 0.475768, -0.228711}, +{-0.728196, 0.0892794, -0.679529}, +{-0.0686843, -0.0592949, 0.995875}, +{-0.624542, 0.779693, 0.0450231}, +{-0.553125, -0.668476, 0.497185}, +{0.69708, 0.469772, -0.541658}, +{0.085354, -0.706237, -0.702812}, +{0.308169, -0.622133, 0.71971}, +{-0.712534, 0.694112, -0.10249}, +{-0.16661, -0.662629, -0.730181}, +{-0.38104, 0.586075, -0.71507}, +{0.355708, -0.927167, -0.117616}, +{-0.349585, -0.754687, 0.555191}, +{-0.344679, -0.925227, 0.158591}, +{0.958463, -0.127976, -0.254892}, +{0.277561, -0.406741, -0.870357}, +{0.271495, -0.783638, 0.55875}, +{0.721991, -0.365288, 0.587617}, +{-0.780681, 0.458752, 0.424362}, +{-0.14267, 0.285737, 0.947628}, +{-0.847507, -0.516206, 0.123544}, +{0.17679, -0.0355374, -0.983607}, +{-0.00886629, -0.981831, 0.189548}, +{-0.83529, 0.134398, 0.53313}, +{-0.209787, 0.710618, -0.671574}, +{-0.688872, -0.574981, 0.441421}, +{0.000579968, -0.798947, 0.601401}, +{0.022314, 0.999066, 0.0369917}, +{0.569746, -0.777583, -0.265996}, +{-0.432933, -0.275586, 0.858266}, +{-0.168233, 0.664311, 0.728278}, +{0.143881, 0.930776, -0.336085}, +{-0.739684, 0.339837, 0.580842}, +{0.402484, -0.854786, -0.32764}, +{0.188465, 0.536313, -0.822708}, +{-0.893367, -0.167853, 0.416799}, +{0.0463878, -0.989504, -0.136859}, +{0.891515, -0.146137, -0.428772}, +{-0.270978, 0.403364, 0.873995}, +{0.909623, -0.312123, -0.274165}, +{-0.0645389, -0.764963, 0.640832}, +{-0.860568, -0.127019, -0.493243}, +{0.412006, 0.401924, -0.817746}, +{-0.856483, -0.377985, 0.351518}, +{-0.519026, -0.769465, 0.372203}, +{-0.281538, -0.153918, 0.947125}, +{0.535697, 0.0817147, -0.840447}, +{0.176875, 0.98376, -0.0305112}, +{-0.953555, -0.266499, -0.140398}, +{-0.729252, 0.398091, 0.556521}, +{0.905964, 0.156002, -0.393564}, +{-0.355032, 0.0749636, 0.931844}, +{-0.223214, -0.434299, 0.872674}, +{-0.944726, -0.289382, -0.154116}, +{-0.448393, 0.674572, -0.586427}, +{-0.712834, -0.41273, 0.567029}, +{0.79357, -0.0793679, -0.603281}, +{0.264879, 0.242192, 0.933371}, +{-0.0846282, 0.286725, -0.954268}, +{0.929102, 0.138991, 0.342711}, +{-0.227705, -0.956085, 0.184533}, +{-0.187085, 0.205396, 0.960631}, +{0.859723, -0.0182001, 0.510436}, +{0.54603, 0.371129, 0.751075}, +{-0.58917, -0.80652, 0.0490214}, +{-0.80694, -0.5828, 0.0958714}, +{0.053987, -0.489671, -0.870234}, +{0.539427, -0.560059, -0.628771}, +{-0.719461, 0.400497, -0.567431}, +{0.0885711, -0.995959, -0.0148576}, +{-0.448824, 0.696023, -0.560454}, +{0.0815733, 0.947985, -0.307686}, +{0.565003, 0.408393, 0.716929}, +{-0.98329, -0.0905731, 0.157918}, +{0.841385, 0.539693, -0.0283297}, +{0.550516, -0.412127, 0.726005}, +{0.295579, 0.305102, -0.905288}, +{0.391698, 0.697622, -0.599913}, +{-0.810178, 0.408264, -0.420634}, +{0.790772, 0.391651, 0.470413}, +{0.109123, 0.984543, -0.136996}, +{-0.978166, 0.128207, -0.163567}, +{-0.6696, -0.230418, 0.706076}, +{0.849107, -0.429713, 0.307188}, +{0.625393, -0.639498, -0.447131}, +{-0.775775, 0.382199, 0.502093}, +{-0.128569, 0.393416, 0.910326}, +{-0.201091, 0.282941, 0.93782}, +{-0.143928, 0.72237, 0.676363}, +{-0.395031, 0.313312, 0.863589}, +{0.0487655, -0.987901, 0.14722}, +{0.788709, 0.421011, -0.447982}, +{-0.998818, -0.0137354, -0.0466272}, +{-0.467379, 0.884057, -6.55763e-05}, +{-0.158109, 0.469027, -0.868916}, +{0.921152, 0.32576, 0.212976}, +{-0.716247, -0.421887, -0.555879}, +{-0.2676, 0.922002, -0.279825}, +{0.709843, 0.698078, -0.0938618}, +{-0.421407, -0.835698, 0.352172}, +{-0.588552, 0.33064, -0.737756}, +{-0.874213, 0.283041, 0.394511}, +{0.959154, 0.180283, 0.217993}, +{0.978598, -0.166951, -0.120305}, +{0.592886, -0.768003, 0.242194}, +{0.955255, -0.134192, 0.26359}, +{-0.897975, 0.431359, 0.0870076}, +{-0.594419, -0.492108, -0.636}, +{-0.0661818, 0.993617, -0.0913517}, +{0.0724074, 0.983393, 0.16642}, +{0.794781, 0.470349, 0.38353}, +{-0.842681, 0.538412, -0.00119135}, +{0.942937, -0.2129, -0.256016}, +{0.497282, 0.086389, -0.863277}, +{0.0376349, -0.954772, 0.294949}, +{-0.808374, 0.0377819, 0.587456}, +{-0.0538058, 0.563107, 0.82463}, +{0.139309, 0.947273, 0.288559}, +{-0.413041, 0.737929, 0.533721}, +{0.93779, 0.331418, 0.103501}, +{-0.307696, -0.9472, 0.0901992}, +{-0.351043, -0.352512, 0.86747}, +{0.44842, -0.0205, -0.893588}, +{0.98002, -0.18045, -0.0836551}, +{-0.783922, -0.378148, -0.492413}, +{0.254744, 0.0321739, 0.966473}, +{-0.361177, 0.717579, -0.59551}, +{-0.287109, 0.929941, 0.229735}, +{-0.796506, 0.603541, -0.0362831}, +{0.213866, 0.805984, -0.551952}, +{-0.206413, -0.886837, -0.413418}, +{-0.942808, -0.177519, -0.282134}, +{-0.953864, -0.047572, 0.296448}, +{-0.108725, -0.80676, 0.580791}, +{0.655167, -0.575531, 0.489408}, +{-0.26821, -0.961313, -0.062776}, +{0.4394, 0.77673, 0.451241}, +{0.734085, -0.324041, 0.596755}, +{-0.120679, 0.949922, 0.288246}, +{0.552587, 0.831591, 0.0557122}, +{-0.145806, -0.201449, 0.968586}, +{-0.348096, 0.325704, -0.879059}, +{-0.363671, -0.854669, -0.370519}, +{-0.609951, -0.768363, 0.193851}, +{-0.0428035, 0.981242, -0.187966}, +{0.199488, -0.956854, 0.21127}, +{0.354041, -0.361824, -0.862403}, +{-0.700101, -0.711521, -0.0599708}, +{0.669507, -0.0823363, 0.738229}, +{-0.973178, 0.17072, -0.154206}, +{-0.844855, -0.391564, 0.364552}, +{-0.964624, -0.254598, 0.0684139}, +{0.715911, -0.683872, -0.140676}, +{-0.869399, -0.269567, -0.4141}, +{-0.910118, 0.0397934, 0.412435}, +{0.599398, 0.654847, -0.460323}, +{-0.858494, 0.373578, 0.351324}, +{0.20047, -0.622694, -0.756349}, +{-0.791004, 0.191885, -0.580941}, +{0.284342, -0.674869, 0.680956}, +{-0.858334, -0.506982, 0.0789444}, +{0.269879, 0.698302, 0.662977}, +{0.323858, -0.766505, 0.554605}, +{-0.707329, 0.6999, 0.0991286}, +{0.867644, 0.0826637, -0.490265}, +{0.227606, 0.856332, 0.463564}, +{0.30059, -0.944471, 0.132739}, +{-0.637241, 0.0615798, -0.7682}, +{0.638011, 0.645645, 0.419623}, +{-0.927098, -0.230183, -0.295813}, +{-0.659675, 0.301357, 0.688486}, +{-0.777926, 0.628039, -0.0199492}, +{-0.605243, -0.115895, -0.787559}, +{-0.0654718, 0.962462, 0.263402}, +{-0.352416, 0.86862, -0.348285}, +{-0.110457, -0.806032, -0.581474}, +{0.197528, 0.750607, 0.630533}, +{0.755926, -0.536331, -0.375399}, +{0.821056, 0.223586, -0.525239}, +{-0.664956, 0.253612, -0.702506}, +{0.201518, -0.0328747, 0.978933}, +{-0.209827, -0.73845, 0.640831}, +{0.796235, 0.59036, -0.132227}, +{-0.915725, 0.352343, 0.193139}, +{-0.407448, 0.0368511, 0.912485}, +{0.11229, 0.929486, 0.35135}, +{0.749146, 0.662311, -0.0111464}, +{0.905993, 0.324275, -0.27207}, +{0.407324, -0.890735, -0.201689}, +{0.169786, 0.367227, 0.914504}, +{-0.191178, -0.530373, 0.825927}, +{-0.787989, -0.0355933, -0.614659}, +{-0.144838, -0.955181, -0.258169}, +{0.753115, 0.158211, 0.638582}, +{-0.306448, -0.162808, -0.937861}, +{-0.489577, 0.278288, -0.82636}, +{-0.72246, 0.590467, 0.359723}, +{0.287388, -0.935619, -0.204999}, +{-0.882148, 0.47034, -0.02441}, +{-0.471464, -0.880318, -0.0525503}, +{-0.796874, 0.595351, 0.102711}, +{0.271123, 0.649341, 0.710527}, +{-0.37685, -0.669364, -0.640263}, +{0.703322, 0.686989, -0.182716}, +{0.24842, 0.756239, 0.605302}, +{0.53306, 0.254665, -0.806841}, +{0.842756, -0.421621, -0.334661}, +{-0.208891, -0.977877, 0.0109621}, +{-0.801054, -0.215962, -0.558277}, +{-0.519437, -0.0747833, 0.85123}, +{0.0836781, -0.772379, -0.629625}, +{-0.774349, -0.330209, 0.539764}, +{0.0382409, 0.106411, -0.993587}, +{-0.816217, 0.230689, 0.529691}, +{0.014752, -0.949014, 0.314888}, +{0.431221, 0.894449, -0.118362}, +{-0.956352, -0.225694, 0.185616}, +{0.829542, -0.555226, 0.0598695}, +{-0.386713, 0.907231, -0.165487}, +{-0.901075, -0.17875, -0.39511}, +{-0.749729, -0.64827, -0.132861}, +{-0.562157, -0.146526, -0.813947}, +{0.0990264, 0.0472287, 0.993963}, +{0.36293, 0.589729, -0.721458}, +{0.0500146, 0.124626, -0.990942}, +{0.959005, -0.251085, 0.131401}, +{-0.541522, -0.799311, -0.260492}, +{0.991897, 0.0197292, -0.125507}, +{-0.936784, -0.349463, -0.0176257}, +{0.916422, -0.230106, -0.327446}, +{0.415317, 0.296418, 0.860028}, +{-0.731054, 0.588311, -0.345617}, +{-0.811151, 0.342511, 0.474046}, +{0.255074, 0.890566, 0.376602}, +{-0.231586, -0.623258, 0.746938}, +{-0.734428, -0.384482, -0.559276}, +{0.583332, -0.756235, -0.296365}, +{-0.060311, -0.302921, -0.951105}, +{0.297677, 0.837406, -0.458411}, +{-0.588758, 0.722114, 0.363201}, +{-0.49972, 0.0663795, -0.86364}, +{-0.737663, -0.0863525, -0.669624}, +{0.32931, 0.435062, -0.838019}, +{0.207166, 0.616965, -0.759234}, +{0.689518, 0.0685701, -0.721015}, +{0.391154, -0.868447, -0.304629}, +{0.0164783, -0.731568, 0.681569}, +{0.280243, 0.56566, -0.77556}, +{0.314565, 0.49037, 0.812765}, +{-0.512091, 0.811192, -0.282368}, +{-0.564512, 0.817313, -0.115436}, +{0.99433, 0.0633357, -0.0854221}, +{-0.0500625, -0.671179, -0.739603}, +{0.816401, -0.529016, 0.231583}, +{0.252949, 0.942806, 0.217103}, +{-0.70603, 0.139916, -0.694223}, +{-0.61635, -0.397144, 0.679992}, +{0.731873, -0.540254, 0.415316}, +{-0.807739, 0.369344, -0.459503}, +{0.0360823, -0.955668, 0.292228}, +{0.616608, -0.449479, -0.646346}, +{-0.942669, -0.3187, 0.0990219}, +{-0.341522, 0.566341, -0.75008}, +{-0.8386, -0.263182, 0.476954}, +{-0.270799, -0.0591661, -0.960816}, +{0.837619, -0.53241, 0.122207}, +{-0.765306, -0.349541, -0.540488}, +{0.649025, -0.217449, -0.729028}, +{-0.557545, -0.513753, -0.652075}, +{0.157804, -0.987459, 0.0048134}, +{0.693179, 0.507162, 0.512142}, +{0.382248, 0.906845, 0.177537}, +{-0.560155, 0.645224, 0.519531}, +{0.474783, -0.87827, -0.0567782}, +{-0.813674, -0.243869, 0.527695}, +{-0.355341, -0.390255, 0.849373}, +{0.655591, 0.736493, 0.166671}, +{0.0197052, -0.356799, -0.933973}, +{0.980937, 0.00742103, 0.194183}, +{-0.229748, -0.270781, 0.934823}, +{-0.265678, -0.686185, -0.677174}, +{-0.450957, 0.721692, -0.525165}, +{0.000324749, -0.866542, -0.499104}, +{-0.553754, -0.680856, -0.479367}, +{-0.264192, 0.796437, -0.543958}, +{-0.392973, 0.741755, -0.543482}, +{0.702389, 0.697604, -0.141416}, +{0.714674, -0.469297, 0.518654}, +{-0.402911, 0.421222, -0.812549}, +{0.264147, -0.68831, -0.675615}, +{0.78921, -0.582286, -0.19517}, +{-0.0824477, -0.00116425, 0.996595}, +{-0.324806, 0.840263, -0.43412}, +{0.887715, 0.129521, 0.441799}, +{0.597744, 0.79876, -0.0684474}, +{-0.207112, -0.275587, 0.938699}, +{-0.575629, 0.599987, -0.555578}, +{0.719725, 0.397451, 0.569235}, +{-0.959087, -0.076287, -0.272641}, +{0.796882, -0.354669, 0.489069}, +{0.708458, 0.448478, 0.544935}, +{-0.665336, 0.516664, 0.538875}, +{-0.911548, 0.0911464, -0.400964}, +{0.776899, -0.614433, -0.137477}, +{-0.871522, -0.170681, -0.459693}, +{-0.870652, 0.354273, -0.341256}, +{0.0942658, -0.9856, -0.140379}, +{0.726113, 0.686427, 0.0397239}, +{-0.397278, -0.822286, 0.40745}, +{-0.697302, -0.298496, -0.651667}, +{0.191794, -0.947671, 0.255215}, +{0.735911, -0.645858, 0.203229}, +{0.0600239, 0.53747, 0.841144}, +{0.14828, 0.615972, 0.773687}, +{-0.134888, 0.353702, -0.925581}, +{-0.069339, -0.991692, 0.10835}, +{0.589593, -0.0975273, 0.801791}, +{0.783441, -0.16331, 0.599625}, +{0.527139, 0.745402, 0.408044}, +{0.102921, -0.11983, -0.987445}, +{-0.0599521, -0.851733, 0.520535}, +{-0.608276, 0.784458, 0.120939}, +{-0.515247, -0.000222423, -0.857042}, +{-0.61477, -0.358084, -0.702733}, +{0.0994203, 0.0483643, -0.993869}, +{-0.75529, 0.653116, 0.0545559}, +{-0.503755, -0.250575, 0.826706}, +{-0.899874, 0.118314, 0.419796}, +{-0.688041, -0.669725, 0.279408}, +{0.0910559, 0.6365, -0.765883}, +{0.0381416, 0.975596, 0.216237}, +{-0.265139, -0.703951, 0.658904}, +{0.978556, 0.0848833, 0.187678}, +{0.14083, -0.621125, 0.770955}, +{-0.176196, 0.973474, 0.145955}, +{0.565634, -0.057248, 0.822667}, +{0.753695, -0.575597, -0.317225}, +{0.887132, -0.299991, -0.350717}, +{-0.230457, -0.156426, 0.960427}, +{0.701721, -0.474333, -0.531598}, +{-0.110296, 0.630341, -0.768443}, +{0.501116, 0.824596, 0.262533}, +{-0.374546, -0.637327, 0.673446}, +{-0.383725, 0.464229, -0.798277}, +{0.0580225, -0.967883, 0.244615}, +{0.551548, 0.560137, -0.618095}, +{-0.101881, -0.46053, -0.881778}, +{-0.612479, -0.7873, 0.0709087}, +{0.390486, 0.852635, 0.347182}, +{0.458075, 0.371163, 0.807716}, +{0.613617, -0.783779, -0.0957313}, +{-0.556525, 0.400959, 0.727675}, +{0.499381, 0.682695, 0.533429}, +{0.984063, 0.150016, 0.0954694}, +{0.471225, -0.88199, -0.00636732}, +{-0.811055, -0.450552, -0.373085}, +{-0.566329, 0.648078, -0.509182}, +{-0.748088, 0.288496, -0.597608}, +{-0.549832, 0.830465, 0.0895167}, +{-0.0493562, 0.876603, -0.478677}, +{0.295538, -0.767647, 0.56866}, +{-0.266756, 0.0178255, 0.963599}, +{-0.344263, -0.130044, -0.929823}, +{0.452946, 0.861469, -0.22959}, +{0.391077, 0.827886, -0.402075}, +{-0.701277, -0.133904, -0.700201}, +{0.632009, 0.673669, -0.383059}, +{0.282575, -0.22528, -0.932416}, +{-0.639501, 0.757424, -0.13171}, +{0.0487892, -0.862856, 0.503089}, +{-0.0426833, -0.405237, -0.913215}, +{-0.307688, 0.466109, 0.8295}, +{-0.0783323, 0.932435, 0.352744}, +{-0.245772, -0.872554, -0.422192}, +{-0.105561, -0.253903, 0.961452}, +{-0.698081, -0.696223, 0.167203}, +{-0.857101, 0.121097, -0.500712}, +{0.0210134, 0.788351, 0.614866}, +{0.5518, 0.175332, 0.815338}, +{0.270618, 0.188587, 0.944034}, +{-0.0780988, 0.641501, -0.763136}, +{-0.99306, 0.0134998, 0.116832}, +{-0.483049, 0.524776, -0.70091}, +{0.750752, -0.31133, 0.582619}, +{0.812438, -0.152253, -0.562818}, +{0.820164, 0.340432, 0.459822}, +{0.158244, 0.369461, 0.915673}, +{0.275361, 0.793618, -0.542537}, +{0.893993, -0.350854, 0.278706}, +{-0.821305, 0.55538, 0.130428}, +{-0.34892, 0.808745, -0.473483}, +{0.556329, -0.805549, 0.203934}, +{0.387127, -0.0419158, 0.921073}, +{0.691026, 0.245518, 0.679856}, +{0.00266877, 0.940652, 0.339361}, +{-0.422848, -0.540347, 0.727478}, +{0.0406423, 0.916294, -0.398438}, +{0.992862, 0.0234687, 0.116939}, +{0.499208, 0.855739, -0.136022}, +{-0.26282, -0.959319, 0.103116}, +{0.721329, 0.403828, -0.562679}, +{-0.802475, -0.419347, 0.424478}, +{-0.671164, -0.426121, 0.606596}, +{-0.961493, 0.145253, -0.233309}, +{0.0934428, -0.268941, 0.958613}, +{-0.554441, -0.827452, -0.08899}, +{0.611902, 0.548183, -0.57015}, +{-0.147962, 0.062953, -0.986987}, +{-0.707904, -0.352186, 0.612239}, +{0.56187, -0.32778, -0.759515}, +{-0.328466, 0.406813, 0.852416}, +{0.26952, -0.311998, 0.911052}, +{0.0490865, 0.373432, 0.926358}, +{0.580108, 0.581883, -0.569988}, +{0.226997, -0.182859, 0.956575}, +{-0.943251, -0.0678514, 0.325076}, +{-0.687003, -0.323428, 0.650708}, +{-0.614447, -0.787446, 0.0488158}, +{-0.00400651, -0.517805, 0.855489}, +{-0.784443, 0.0730019, -0.615889}, +{-0.61713, 0.379243, -0.689438}, +{-0.0100767, 0.658857, 0.7522}, +{0.370795, -0.0119834, -0.928638}, +{0.412538, -0.890299, 0.192823}, +{0.456043, 0.0417046, -0.88898}, +{-0.119861, -0.869084, 0.479924}, +{-0.0910592, 0.850369, -0.518247}, +{-0.289817, -0.618825, -0.730111}, +{-0.454023, -0.81219, -0.366348}, +{0.375905, 0.898207, 0.227857}, +{-0.420898, -0.783026, -0.457947}, +{-0.76111, -0.276577, 0.5867}, +{0.422564, 0.67762, -0.60189}, +{-0.250672, 0.962642, 0.10239}, +{-0.3982, 0.620009, -0.676037}, +{0.760354, 0.632939, -0.145776}, +{0.977373, -0.208068, 0.038065}, +{-0.420687, 0.722836, 0.548206}, +{0.425622, -0.109979, 0.898193}, +{0.10304, -0.98795, -0.115486}, +{0.235739, 0.969748, 0.0633673}, +{-0.914286, -0.335358, -0.227193}, +{0.584215, 0.787032, -0.198177}, +{0.14236, 0.571359, 0.808259}, +{0.5992, 0.333021, 0.728049}, +{-0.22739, 0.53179, 0.815778}, +{-0.658242, 0.538057, -0.52651}, +{0.449638, -0.715824, -0.534249}, +{0.0791765, 0.276292, 0.957807}, +{0.603934, -0.79669, 0.0234157}, +{0.821562, -0.203038, -0.532739}, +{-0.0976596, -0.691315, 0.715923}, +{-0.0589546, 0.673401, -0.736923}, +{-0.914426, 0.241248, -0.325001}, +{0.161471, 0.982652, -0.0912233}, +{-0.336509, 0.397844, 0.853512}, +{-0.420388, 0.873615, -0.245094}, +{0.0640444, 0.84527, 0.530487}, +{-0.0352455, 0.82385, -0.565711}, +{-0.534704, -0.280703, 0.797055}, +{0.885905, 0.262922, -0.382159}, +{-0.0562351, -0.507994, -0.859523}, +{0.592719, 0.734782, 0.329817}, +{0.926818, -0.343725, -0.151199}, +{0.826178, -0.250624, 0.504596}, +{0.931813, -0.34272, -0.119444}, +{-0.342231, -0.443432, 0.8284}, +{0.692905, -0.576394, 0.433188}, +{-0.918566, -0.186893, 0.348293}, +{-0.836432, 0.392508, 0.382517}, +{-0.696492, 0.683553, 0.218299}, +{0.78663, -0.213412, -0.57937}, +{0.588504, 0.345673, -0.730871}, +{-0.477699, -0.63261, -0.609597}, +{-0.97697, -0.10188, 0.187483}, +{0.703413, 0.623665, 0.340956}, +{0.607495, 0.253386, 0.752825}, +{0.37487, -0.32849, 0.86693}, +{-0.4972, -0.169743, 0.85087}, +{0.535864, 0.214694, -0.816552}, +{-0.382474, -0.547716, -0.744124}, +{0.670379, -0.364221, 0.646479}, +{0.203084, -0.470813, -0.858541}, +{0.971462, -0.0613605, -0.229121}, +{-0.96844, 0.160011, 0.191103}, +{-0.137076, -0.347716, -0.927525}, +{0.805887, -0.511273, -0.298573}, +{0.534396, 0.105123, 0.838672}, +{0.279691, 0.441594, -0.852507}, +{-0.37633, -0.633009, -0.676517}, +{-0.846924, 0.500161, 0.180441}, +{-0.480585, 0.875334, 0.0531841}, +{-0.66332, 0.597015, 0.451198}, +{0.444799, -0.234479, 0.864392}, +{0.664007, -0.293749, 0.687609}, +{-0.851509, 0.0515746, 0.521798}, +{0.26895, -0.822463, 0.50122}, +{0.903352, -0.397565, 0.160925}, +{-0.20598, 0.783378, 0.586423}, +{-0.547457, 0.636329, -0.543486}, +{-0.167001, 0.983989, -0.0622531}, +{0.482504, -0.0222271, -0.875611}, +{0.470896, -0.0533223, -0.880576}, +{0.711537, -0.694893, -0.104108}, +{0.599124, -0.55873, -0.573472}, +{-0.474005, -0.797367, 0.37353}, +{0.66566, -0.635605, 0.391028}, +{0.689823, 0.165019, 0.70492}, +{0.857426, -0.163248, 0.488027}, +{0.341714, 0.939745, -0.0105444}, +{0.575619, -0.80597, -0.138111}, +{-0.759326, -0.390086, 0.520823}, +{-0.412759, 0.690952, 0.593478}, +{0.844524, -0.219406, -0.488508}, +{-0.411155, 0.906954, -0.0915791}, +{-0.0734946, 0.814459, -0.575547}, +{-0.318468, -0.112749, 0.941204}, +{0.48634, -0.351264, 0.800054}, +{-0.366569, -0.883893, -0.290449}, +{-0.482288, 0.875982, -0.00729551}, +{-0.586016, 0.191328, -0.787387}, +{-0.0589872, -0.861879, -0.503671}, +{0.619175, -0.27122, 0.736927}, +{-0.431462, 0.851609, 0.297661}, +{-0.519697, 0.769096, -0.37203}, +{0.979585, -0.176167, 0.0968469}, +{0.139623, -0.0885551, 0.986237}, +{0.74248, -0.412116, 0.528095}, +{-0.289222, 0.925695, -0.243802}, +{0.843438, -0.13667, -0.519551}, +{-0.0551621, 0.987593, -0.147028}, +{0.405689, 0.904573, 0.131012}, +{-0.659906, -0.28067, -0.696956}, +{0.875922, 0.0394177, -0.480839}, +{0.214099, 0.954068, -0.20956}, +{-0.807528, 0.0768144, 0.584806}, +{-0.909901, -0.126205, -0.395161}, +{0.34116, 0.418761, 0.841575}, +{-0.28513, 0.947921, -0.141939}, +{-0.706284, -0.576038, -0.411513}, +{0.85137, -0.317163, -0.417823}, +{0.0188583, 0.107301, 0.994048}, +{0.64713, 0.649876, -0.398603}, +{0.138254, 0.724492, -0.675275}, +{0.354867, 0.400039, -0.845008}, +{-0.938112, -0.0234211, 0.34554}, +{0.742036, 0.191766, 0.642346}, +{0.856621, 0.5028, 0.11573}, +{-0.434021, -0.0227692, -0.900615}, +{-0.601026, 0.741429, -0.298414}, +{-0.673475, -0.732513, 0.0992782}, +{-0.662541, -0.379582, 0.645722}, +{-0.995907, 0.0250044, 0.0868546}, +{-0.782266, -0.264561, 0.563974}, +{0.200858, -0.835267, -0.511845}, +{-0.949537, -0.309475, -0.0510352}, +{-0.388429, -0.723384, 0.570823}, +{-0.320672, -0.276452, 0.905949}, +{-0.357194, -0.933937, -0.0131631}, +{0.0885846, 0.953358, 0.288549}, +{-0.145435, 0.657618, -0.73918}, +{0.652278, -0.618788, 0.437761}, +{-0.22985, 0.171973, -0.957911}, +{-0.578777, -0.781129, 0.23421}, +{-0.94286, 0.111042, -0.314142}, +{0.937126, 0.181461, 0.298104}, +{-0.179534, -0.38247, -0.906358}, +{-0.335612, 0.11261, -0.935245}, +{-0.668934, 0.675692, -0.309786}, +{0.0649965, 0.76264, -0.643549}, +{0.576883, -0.80071, -0.161458}, +{0.364489, -0.853905, 0.371477}, +{-0.112131, 0.0117413, 0.993624}, +{0.396367, -0.766435, -0.505441}, +{-0.0716628, -0.921908, 0.380723}, +{0.460602, -0.456948, -0.760949}, +{-0.0161275, -0.210673, 0.977424}, +{0.0955965, -0.113385, 0.988941}, +{-0.0706746, 0.551821, -0.830963}, +{0.56705, 0.802861, 0.184035}, +{0.868758, 0.4178, 0.265899}, +{0.412118, 0.793233, -0.448263}, +{0.717836, 0.286747, 0.634419}, +{0.879291, -0.0473743, -0.473922}, +{0.435926, 0.875095, -0.210183}, +{-0.716205, -0.547931, -0.432228}, +{-0.664407, 0.444246, 0.601007}, +{-0.754116, 0.486444, -0.441227}, +{0.396667, 0.903096, 0.164537}, +{-0.400434, -0.890879, -0.214447}, +{-0.745848, -0.236597, -0.622681}, +{-0.227406, 0.281604, -0.932194}, +{-0.793889, -0.498427, -0.348297}, +{0.157677, 0.90256, 0.400653}, +{0.764759, -0.0358843, 0.643317}, +{-0.75116, -0.180826, 0.634871}, +{0.202384, -0.892214, 0.403727}, +{-0.18349, -0.942192, -0.280366}, +{0.874438, 0.413068, 0.254428}, +{-0.446354, -0.801995, 0.396954}, +{0.302395, 0.232331, 0.924435}, +{0.9458, -0.00947256, 0.324611}, +{-0.498827, -0.301202, -0.81268}, +{-0.819413, -0.56827, -0.0750448}, +{0.613679, 0.269306, -0.742208}, +{0.729911, 0.679867, 0.0707929}, +{-0.513902, 0.133425, -0.84741}, +{0.793504, -0.418047, 0.442252}, +{0.867076, -0.0788834, -0.49189}, +{0.0729151, 0.962485, -0.261355}, +{0.0547217, 0.997401, -0.0468684}, +{0.261088, -0.193653, 0.945691}, +{0.340297, -0.76409, -0.548056}, +{-0.13768, 0.967915, 0.2102}, +{-0.562329, 0.773879, 0.291371}, +{0.0665066, -0.160785, 0.984746}, +{-0.202433, -0.602326, 0.772156}, +{-0.272733, -0.0856947, 0.958266}, +{0.129646, 0.476455, 0.869588}, +{-0.86035, -0.424429, -0.282239}, +{-0.760899, -0.476438, -0.4405}, +{-0.884681, -0.424358, -0.193027}, +{-0.0335591, -0.964762, -0.260975}, +{0.774665, 0.180569, -0.606043}, +{0.126965, -0.983371, 0.129854}, +{0.855729, -0.447714, 0.259383}, +{-0.0957576, 0.47037, 0.877259}, +{0.727538, -0.670938, 0.143288}, +{-0.35429, 0.761615, -0.542607}, +{-0.15773, -0.602513, -0.782368}, +{-0.216273, -0.873181, 0.436785}, +{0.771473, -0.195369, -0.605525}, +{-0.585473, 0.00406065, -0.810682}, +{-0.629023, 0.282473, -0.724251}, +{-0.514511, -0.0281528, -0.857022}, +{-0.322343, 0.0095765, 0.946575}, +{-0.328835, -0.917579, 0.22342}, +{-0.877527, 0.170455, 0.448209}, +{-0.550416, 0.820332, 0.155233}, +{-0.53004, -0.373799, 0.761139}, +{0.991286, -0.0512702, -0.121341}, +{0.292256, -0.907491, -0.301741}, +{-0.447908, -0.893356, 0.0359559}, +{0.452801, -0.891305, 0.0233921}, +{0.232876, -0.925686, -0.298117}, +{0.690013, 0.0977649, 0.717164}, +{-0.79073, -0.162773, 0.590128}, +{0.778195, 0.347253, -0.523286}, +{0.999156, -0.0351757, 0.0211937}, +{-0.473106, 0.0664528, 0.878496}, +{-0.132027, 0.413395, -0.900929}, +{0.820559, -0.295866, -0.489026}, +{-0.963961, 0.251308, 0.0873173}, +{-0.295447, -0.248527, 0.922467}, +{-0.175424, 0.214746, -0.960787}, +{0.2743, -0.262314, -0.925176}, +{-0.237052, -0.545282, 0.804036}, +{0.830063, 0.294873, 0.473335}, +{-0.386738, -0.651831, 0.652342}, +{0.512608, 0.622919, 0.590936}, +{0.88089, -0.404167, 0.246337}, +{0.128466, 0.802582, -0.582545}, +{0.170673, 0.964959, -0.199311}, +{-0.672145, 0.22923, 0.704042}, +{-0.921697, 0.312007, 0.230493}, +{0.707309, 0.573764, 0.412927}, +{-0.18929, 0.543182, 0.817999}, +{0.959745, -0.040652, -0.277917}, +{-0.153033, 0.869658, 0.469335}, +{-0.375193, 0.608997, 0.698822}, +{0.53271, 0.842274, -0.0824327}, +{0.131195, -0.48388, -0.865245}, +{0.602316, 0.797635, 0.0315262}, +{0.136939, -0.184944, 0.973162}, +{0.994643, -0.0319982, 0.0982889}, +{-0.444215, 0.891042, 0.093364}, +{0.988759, -0.117902, -0.091952}, +{-0.548264, 0.177387, -0.817276}, +{-0.339645, 0.386917, -0.857284}, +{0.689515, -0.102579, -0.716971}, +{-0.91773, -0.392896, -0.058344}, +{0.106747, 0.0735383, 0.991563}, +{0.0276432, -0.708076, -0.705595}, +{-0.38965, -0.467323, -0.793588}, +{-0.0131298, 0.895868, -0.444126}, +{0.665809, -0.625251, -0.407136}, +{-0.12515, -0.894627, 0.42893}, +{-0.833045, 0.478715, -0.27725}, +{0.454395, 0.772666, 0.443297}, +{0.987237, -0.00730941, -0.15909}, +{0.414135, -0.57278, -0.7074}, +{-0.324942, -0.72619, -0.605856}, +{0.627281, -0.510358, 0.588263}, +{0.439789, -0.484421, -0.756255}, +{-0.652242, 0.757978, 0.00704881}, +{-0.373942, 0.899618, -0.225509}, +{0.825105, 0.562852, 0.0489898}, +{-0.63233, -0.767614, 0.104532}, +{0.820989, -0.569476, -0.0409113}, +{0.105557, -0.672073, 0.732923}, +{-0.546815, -0.643188, -0.536006}, +{-0.98421, 0.122936, -0.127347}, +{-0.937835, -0.301199, -0.172468}, +{-0.798297, 0.331381, 0.502901}, +{0.876577, -0.320116, 0.359359}, +{-0.873652, -0.0792937, -0.480047}, +{0.872683, -0.331316, 0.358683}, +{-0.0756679, 0.911254, -0.404834}, +{-0.628316, 0.754725, -0.188702}, +{0.90848, 0.0959191, -0.406772}, +{-0.611576, -0.614064, 0.4989}, +{0.665643, -0.228774, 0.710339}, +{0.296074, 0.147245, -0.943748}, +{-0.771641, -0.186052, -0.608239}, +{0.0631991, -0.186638, -0.980394}, +{0.327353, 0.754274, -0.569131}, +{-0.496307, -0.0897301, 0.863497}, +{0.114655, -0.640038, -0.75974}, +{0.95382, 0.253717, 0.160795}, +{0.00368731, 0.0292344, -0.999566}, +{-0.225998, 0.506869, 0.831871}, +{0.0155505, -0.628994, 0.777255}, +{-0.164043, 0.800941, 0.575833}, +{-0.8012, -0.520804, 0.294689}, +{-0.37557, -0.652561, 0.658112}, +{0.565823, 0.614945, 0.549261}, +{0.176254, 0.966684, -0.185625}, +{0.390336, -0.748052, 0.536708}, +{0.818069, -0.557636, -0.140732}, +{-0.517473, 0.349365, -0.781131}, +{-0.162016, -0.450023, -0.878197}, +{0.577226, 0.289765, -0.763444}, +{-0.690688, -0.629412, -0.356078}, +{0.482653, -0.630833, 0.607532}, +{-0.858363, 0.282404, -0.428323}, +{0.310449, 0.94967, -0.0418089}, +{-0.722421, 0.194888, 0.663421}, +{-0.644558, 0.485278, -0.590805}, +{-0.162559, -0.965168, 0.205}, +{0.581464, 0.755073, -0.302925}, +{-0.898809, 0.427672, 0.0961232}, +{0.404517, -0.755253, -0.515711}, +{-0.270952, 0.790808, 0.548824}, +{-0.908789, 0.338761, -0.243603}, +{-0.529178, -0.212368, -0.821505}, +{-0.220407, 0.390744, 0.893723}, +{-0.932191, -0.0588506, -0.35715}, +{-0.98006, -0.182215, -0.0792498}, +{0.30589, -0.529102, 0.791507}, +{0.795032, 0.332284, -0.507456}, +{0.178782, 0.982033, 0.0604059}, +{0.147488, -0.600992, -0.785529}, +{0.495385, -0.21943, -0.840502}, +{-0.397787, -0.91367, 0.0834989}, +{0.973768, -0.225909, -0.0272409}, +{0.831056, 0.149098, -0.535832}, +{0.517933, 0.831696, 0.200068}, +{0.349003, -0.420383, -0.837541}, +{0.954388, 0.120516, -0.273164}, +{-0.427603, -0.457066, 0.779902}, +{0.742244, 0.223035, -0.631925}, +{-0.72167, 0.633715, 0.278565}, +{0.767102, -0.105158, 0.632848}, +{-0.325012, 0.941518, -0.0889433}, +{0.704233, 0.692471, 0.156652}, +{0.754003, -0.444778, -0.483377}, +{0.865427, 0.41148, -0.285866}, +{0.457081, -0.759775, 0.462406}, +{-0.790932, -0.378616, -0.480704}, +{0.0755721, 0.996668, 0.0307027}, +{-0.23453, 0.617231, -0.751013}, +{0.238734, 0.820636, -0.519194}, +{0.0536378, 0.312401, 0.948435}, +{-0.708959, -0.703884, 0.0438579}, +{0.240132, -0.94957, 0.201627}, +{0.502017, 0.640009, 0.581693}, +{-0.307768, 0.0268541, -0.951082}, +{0.209938, -0.770832, -0.601452}, +{-0.322063, -0.165027, 0.932224}, +{-0.235302, -0.97144, -0.0305988}, +{-0.0350638, -0.556001, -0.830442}, +{0.969674, -0.204291, 0.134154}, +{0.0810517, -0.981262, 0.174803}, +{0.501862, -0.859798, -0.0942477}, +{0.784721, -0.465198, 0.409639}, +{-0.0843505, 0.238352, -0.967509}, +{-0.320688, -0.0431963, 0.946199}, +{-0.290557, -0.662391, -0.690517}, +{0.0693549, 0.442012, -0.894324}, +{-0.371877, -0.429114, -0.823146}, +{0.655676, -0.594403, -0.465591}, +{0.0548481, 0.80823, 0.586307}, +{0.330308, -0.124721, 0.935597}, +{-0.226417, 0.0238827, 0.973738}, +{-0.74498, 0.574555, 0.338956}, +{-0.527968, -0.480777, 0.700074}, +{-0.57229, -0.460331, -0.67866}, +{0.570791, 0.71101, -0.410685}, +{-0.376382, 0.828, -0.415634}, +{-0.921773, -0.374459, 0.100576}, +{0.495866, -0.181019, 0.849322}, +{0.490444, -0.00188075, 0.871471}, +{-0.872174, -0.315783, -0.373623}, +{-0.395047, 0.872859, -0.286452}, +{-0.548521, 0.769207, -0.327789}, +{0.561721, 0.826992, -0.023511}, +{-0.118127, -0.28615, -0.950875}, +{0.986498, 0.150675, -0.0641796}, +{0.0918252, -0.934555, 0.343767}, +{-0.0928584, 0.440774, 0.892802}, +{0.930205, -0.152155, -0.334018}, +{-0.588075, 0.38693, 0.710248}, +{0.918815, -0.370693, 0.135521}, +{0.762426, -0.524987, -0.378279}, +{0.0127153, 0.750318, -0.660954}, +{-0.820224, -0.0701893, 0.56772}, +{-0.378097, 0.275756, -0.883743}, +{-0.84762, -0.325487, 0.419044}, +{0.811966, 0.549029, -0.198189}, +{0.794764, -0.209259, 0.569703}, +{-0.184438, -0.797585, -0.574318}, +{0.705616, -0.612879, -0.355647}, +{-0.232908, -0.0979754, 0.967551}, +{0.566753, -0.204169, -0.798189}, +{-0.421614, 0.621579, 0.660214}, +{-0.73206, 0.102491, 0.673486}, +{0.546562, 0.00251961, 0.837415}, +{-0.0432573, 0.661303, 0.74887}, +{-0.0630739, -0.452173, 0.889697}, +{-0.587725, -0.260512, -0.765971}, +{0.961765, 0.00292025, -0.273859}, +{-0.538014, -0.147449, 0.82994}, +{-0.298212, 0.254243, -0.920016}, +{-0.639355, 0.368771, -0.67471}, +{0.25556, 0.0358078, -0.96613}, +{-0.342135, -0.0838824, 0.935899}, +{-0.764652, 0.103189, 0.636129}, +{0.554393, 0.424371, -0.715931}, +{0.184816, -0.39865, 0.898288}, +{-0.911218, 0.0874296, 0.402539}, +{0.721785, -0.356053, -0.593509}, +{0.834466, 0.260504, -0.485598}, +{-0.669452, 0.742262, -0.0296849}, +{-0.562414, 0.215582, -0.798257}, +{-0.388226, 0.185776, -0.902645}, +{0.280494, 0.850822, 0.444326}, +{-0.22455, -0.943729, 0.242803}, +{0.798238, -0.401392, -0.449111}, +{0.922097, 0.230165, -0.311064}, +{0.557576, 0.697959, 0.449403}, +{0.179823, -0.970894, 0.158205}, +{-0.64033, -0.507953, 0.57616}, +{-0.47583, 0.0516727, 0.878018}, +{-0.882236, 0.442312, 0.161306}, +{0.0858194, -0.752397, 0.653095}, +{0.130744, -0.651422, 0.747366}, +{-0.00733291, -0.587168, 0.809432}, +{-0.870417, 0.341242, 0.354864}, +{0.500817, 0.466707, -0.728949}, +{-0.616696, -0.726973, -0.301988}, +{-0.181508, -0.453847, -0.872398}, +{0.946023, -0.316608, 0.0692826}, +{0.311464, -0.297936, 0.902344}, +{0.23938, 0.837971, 0.49041}, +{-0.674039, -0.729612, -0.115492}, +{0.922931, 0.169901, 0.345444}, +{0.17907, -0.197022, -0.963907}, +{0.782485, 0.591237, -0.195337}, +{0.507399, 0.859585, 0.0605002}, +{-0.589234, -0.731746, 0.342564}, +{-0.713209, 0.510142, -0.480716}, +{-0.577928, -0.196155, 0.792163}, +{0.870836, -0.228715, 0.435125}, +{-0.0664052, 0.993473, 0.0927448}, +{0.959015, 0.277223, -0.0586296}, +{-0.447561, 0.223451, 0.865886}, +{0.230264, -0.910599, -0.343203}, +{-0.719232, 0.670808, -0.180888}, +{0.549912, 0.629191, -0.549286}, +{0.687342, 0.620961, -0.376787}, +{0.383132, 0.302502, -0.872756}, +{0.776146, -0.339611, 0.531284}, +{-0.679159, 0.170699, 0.713866}, +{0.738795, 0.608519, -0.289632}, +{0.313729, -0.844494, -0.434055}, +{-0.220112, 0.871649, -0.437926}, +{0.555705, -0.812149, -0.177782}, +{0.0112816, -0.102011, 0.994719}, +{-0.345411, -0.180906, -0.92085}, +{-0.174787, 0.750139, -0.637762}, +{-0.528849, 0.446578, -0.721725}, +{0.592541, 0.123276, 0.796051}, +{0.381792, 0.548896, 0.743605}, +{-0.761683, -0.60261, -0.238117}, +{-0.343907, -0.910046, 0.231398}, +{-0.792326, 0.217923, 0.569851}, +{-0.632527, 0.733766, -0.247986}, +{0.774706, 0.133392, -0.618092}, +{0.522803, 0.782921, -0.337211}, +{0.796652, 0.0291944, -0.603733}, +{-0.618471, 0.694867, 0.36695}, +{0.77107, 0.461472, -0.438742}, +{0.988064, 0.110682, 0.107143}, +{-0.842061, -0.510938, -0.172846}, +{-0.129952, -0.482301, 0.866313}, +{-0.675298, 0.366019, 0.640315}, +{-0.417112, 0.476997, 0.773622}, +{0.155813, -0.0265306, 0.98743}, +{0.798572, -0.548248, 0.248407}, +{-0.153654, -0.94499, 0.288763}, +{0.18851, -0.980011, -0.0635725}, +{0.148146, -0.987498, 0.0538528}, +{-0.64539, 0.576885, 0.500675}, +{-0.330543, 0.692125, -0.641642}, +{0.354902, 0.429512, 0.8304}, +{-0.813098, 0.452218, -0.366566}, +{0.633344, -0.317279, 0.705839}, +{-0.0333403, -0.91423, -0.403821}, +{0.870271, 0.134948, -0.473727}, +{0.481163, 0.864814, -0.143452}, +{-0.798512, 0.537712, -0.270635}, +{0.75664, 0.00341982, 0.653822}, +{-0.425424, -0.598962, -0.678424}, +{-0.726417, 0.462847, 0.508027}, +{0.970769, -0.238527, 0.0267021}, +{-0.90008, 0.268152, -0.343439}, +{0.440394, -0.891815, -0.103539}, +{0.940944, -0.146349, -0.305298}, +{-0.871284, 0.481859, -0.0931483}, +{0.613643, -0.288044, 0.735169}, +{-0.579423, 0.808823, -0.100371}, +{0.67157, -0.701429, 0.238729}, +{-0.889215, -0.33559, 0.310929}, +{-0.595047, -0.779682, -0.194974}, +{-0.765451, 0.53547, 0.356871}, +{-0.60529, 0.71386, -0.352175}, +{-0.752973, 0.241486, 0.612141}, +{0.577529, -0.670834, -0.465234}, +{0.901541, 0.214379, 0.375854}, +{0.833721, -0.216135, 0.508129}, +{0.128911, -0.0673679, -0.989365}, +{0.322106, 0.235088, -0.917051}, +{-0.595395, 0.107992, 0.796142}, +{0.318106, 0.441336, 0.839066}, +{-0.593565, -0.684176, 0.423773}, +{-0.413223, 0.782086, -0.466463}, +{0.305657, -0.322528, -0.895851}, +{-0.368517, 0.881428, 0.295433}, +{-0.181184, 0.927436, 0.327162}, +{-0.282772, 0.955543, 0.0835317}, +{0.960188, -0.278661, -0.0196872}, +{-0.800918, 0.327066, -0.501556}, +{0.626262, 0.0123683, -0.779514}, +{-0.832885, 0.540322, -0.119809}, +{0.90119, 0.400752, -0.165092}, +{-0.378266, 0.920926, 0.0938635}, +{0.0473079, -0.416434, 0.907934}, +{0.942935, 0.321005, 0.0884819}, +{-0.0999173, -0.157577, -0.982439}, +{0.318958, -0.2874, 0.903143}, +{0.0460037, -0.259299, 0.964701}, +{-0.253818, -0.365588, -0.895501}, +{-0.105723, -0.118997, -0.98725}, +{0.0760366, -0.656088, 0.750844}, +{-0.968054, -0.247461, -0.0404303}, +{-0.598547, 0.708427, -0.373996}, +{0.42037, 0.369042, 0.828913}, +{-0.935184, -0.158851, -0.316541}, +{0.39322, -0.791305, 0.468204}, +{-0.433578, 0.661417, 0.611995}, +{-0.456742, 0.755348, -0.469932}, +{-0.261466, -0.813283, -0.519813}, +{0.615299, -0.17416, -0.768814}, +{-0.953213, -0.0770189, -0.292324}, +{0.798591, 0.522097, -0.299445}, +{0.0243203, 0.00176753, 0.999703}, +{-0.239502, 0.455102, 0.857625}, +{-0.165432, -0.393871, -0.904156}, +{0.396429, -0.0617446, -0.915987}, +{0.15597, 0.861777, -0.482714}, +{0.375452, -0.92554, -0.0491122}, +{0.317093, -0.947518, 0.0407687}, +{-0.813502, -0.491994, 0.31009}, +{0.0445713, -0.814496, 0.578454}, +{0.525339, 0.0638176, -0.848497}, +{-0.52823, -0.471064, -0.70645}, +{0.932197, 0.0339905, -0.360352}, +{-0.165969, -0.331303, -0.928813}, +{-0.654864, -0.711443, -0.254953}, +{-0.0502693, -0.188867, -0.980715}, +{-0.627421, -0.0463894, 0.777297}, +{0.107303, 0.992174, 0.0638509}, +{0.838842, -0.0606279, 0.540989}, +{-0.315994, -0.738822, 0.595223}, +{0.725319, 0.44671, 0.523796}, +{0.588816, -0.348738, 0.729162}, +{-0.48709, 0.422633, -0.76428}, +{0.745159, -0.629666, 0.219679}, +{-0.706558, -0.552886, 0.441693}, +{-0.0339055, 0.980812, -0.191985}, +{0.0512863, 0.421878, 0.905201}, +{0.326822, 0.86577, 0.378986}, +{0.661461, -0.515112, 0.545096}, +{0.524294, -0.104427, 0.84511}, +{0.428149, -0.498237, 0.753955}, +{0.69557, 0.718083, 0.0232217}, +{0.719113, -0.694867, 0.00591781}, +{0.318743, 0.774906, -0.545824}, +{-0.19302, -0.959042, 0.207322}, +{-0.592197, 0.803931, -0.0547534}, +{0.303717, 0.0771767, -0.949631}, +{-0.374034, 0.240094, 0.895797}, +{-0.880081, 0.26221, 0.395857}, +{0.116794, -0.858554, -0.499244}, +{0.909291, -0.380895, -0.167656}, +{0.135535, -0.862324, 0.487881}, +{-0.676074, 0.736616, -0.017916}, +{-0.540387, 0.0401064, 0.84046}, +{-0.0165481, 0.681485, 0.731645}, +{0.135539, 0.904741, -0.403822}, +{-0.70663, -0.662078, 0.249651}, +{-0.277946, -0.551942, 0.786197}, +{-0.252472, 0.936569, 0.243099}, +{0.212202, 0.890011, -0.403548}, +{0.726276, -0.0147126, -0.687245}, +{-0.507034, -0.819933, -0.265758}, +{-0.833989, 0.389232, -0.391103}, +{-0.443344, -0.687612, 0.575009}, +{0.836122, 0.103498, 0.538692}, +{-0.874235, 0.179487, 0.451108}, +{0.236486, -0.248905, 0.939213}, +{0.404582, 0.271584, 0.873244}, +{-0.458956, 0.789618, 0.407263}, +{-0.645396, 0.170449, 0.744588}, +{0.46963, 0.817569, -0.333209}, +{-0.642875, -0.728478, 0.236711}, +{0.19651, -0.816261, -0.543233}, +{-0.837083, -0.0246678, -0.54652}, +{0.94096, 0.100962, -0.323112}, +{0.253911, -0.709579, 0.657288}, +{0.585565, 0.788747, -0.187062}, +{0.372048, 0.296242, -0.879671}, +{-0.822661, 0.261713, 0.504713}, +{-0.396345, 0.512504, -0.761742}, +{0.857031, 0.51117, 0.0648367}, +{0.640934, -0.757898, 0.121631}, +{-0.210667, 0.966713, 0.145206}, +{-0.306418, -0.547491, -0.778692}, +{-0.756442, 0.150816, -0.636435}, +{0.486667, 0.871331, 0.0627408}, +{-0.863612, 0.469728, -0.183109}, +{0.870743, -0.484281, 0.0853165}, +{-0.228072, 0.971784, 0.0601495}, +{-0.725886, 0.538276, -0.428191}, +{-0.453304, 0.0769717, -0.888026}, +{-0.752347, -0.346085, -0.560535}, +{-0.707147, -0.0358043, -0.706159}, +{0.131625, 0.497126, 0.857636}, +{0.363628, 0.0447506, -0.930469}, +{-0.687752, 0.374049, 0.622161}, +{0.513926, -0.109022, -0.850879}, +{0.938769, -0.326917, 0.108804}, +{0.548046, 0.832461, 0.0815677}, +{-0.47875, 0.831703, -0.281193}, +{-0.978368, -0.153476, -0.138716}, +{0.0399427, 0.409297, 0.911526}, +{-0.600716, -0.442912, 0.66556}, +{0.192134, 0.299201, 0.934646}, +{0.511019, 0.499389, 0.699621}, +{-0.173113, -0.708839, 0.683798}, +{-0.425179, -0.184498, 0.886106}, +{-0.711214, 0.244916, -0.658931}, +{0.359844, 0.720022, 0.593364}, +{0.159352, -0.355233, 0.921095}, +{-0.132973, -0.974565, -0.180391}, +{0.875414, -0.0913639, 0.47466}, +{-0.597893, 0.160109, -0.785423}, +{0.364605, -0.90819, 0.205556}, +{0.06045, 0.45557, 0.888145}, +{0.540111, -0.668289, -0.511536}, +{0.964069, -0.244494, 0.10389}, +{0.629514, 0.0618933, -0.77452}, +{0.946418, -0.0399812, -0.320461}, +{-0.920376, -0.257562, 0.294228}, +{-0.735146, -0.513263, 0.442856}, +{0.590205, -0.788457, 0.173184}, +{-0.353274, -0.930822, 0.0936396}, +{0.164589, 0.044224, 0.98537}, +{0.578352, -0.772022, 0.263611}, +{0.282666, 0.886142, -0.367223}, +{-0.966073, -0.224526, -0.127637}, +{-0.192898, 0.649861, -0.735167}, +{-0.571889, 0.390192, -0.721591}, +{0.569665, 0.781625, -0.254055}, +{-0.870498, -0.120377, 0.477224}, +{0.101627, -0.814895, -0.570629}, +{0.994193, -0.107607, 0.00111603}, +{-0.394179, -0.334274, -0.856086}, +{0.142877, -0.972517, 0.18384}, +{-0.85806, 0.432953, -0.276196}, +{0.157665, 0.229335, 0.960493}, +{-0.135365, -0.971377, -0.195202}, +{0.413552, 0.864748, 0.284931}, +{0.842791, -0.52168, 0.132491}, +{-0.823184, -0.246695, -0.51138}, +{-0.943891, 0.330142, 0.00869059}, +{-0.973575, 0.0822292, 0.213049}, +{-0.776558, -0.611614, -0.151281}, +{-0.506637, -0.485734, -0.712307}, +{0.338201, -0.924644, -0.175081}, +{0.613952, -0.613534, 0.496628}, +{0.402849, -0.228036, -0.886404}, +{0.336843, 0.895443, 0.291065}, +{0.728775, 0.552289, -0.404801}, +{-0.780533, -0.415655, 0.466903}, +{0.765954, -0.633163, -0.11144}, +{-0.545607, 0.819645, 0.174629}, +{0.0178708, -0.607655, -0.794}, +{-0.91995, 0.0994564, -0.379211}, +{0.347758, -0.793667, -0.499156}, +{-0.133425, 0.768088, -0.62629}, +{-0.957524, -0.225799, 0.179339}, +{0.591333, -0.247424, -0.767533}, +{0.147433, -0.674425, 0.723473}, +{-0.0392865, 0.849273, 0.52649}, +{0.412295, 0.392127, -0.822344}, +{-0.0520864, 0.0437563, 0.997684}, +{0.901821, 0.415307, -0.119325}, +{-0.0698159, -0.996132, -0.0533587}, +{0.913715, 0.398909, -0.077436}, +{-0.895175, 0.378432, 0.235481}, +{0.44407, 0.876406, 0.186315}, +{0.897025, 0.00745651, 0.441918}, +{-0.241982, 0.758671, -0.604866}, +{0.444457, 0.71196, 0.543664}, +{0.861931, -0.49813, -0.0945605}, +{0.633682, 0.77033, -0.0709791}, +{-0.788784, -0.613126, -0.0435511}, +{0.679393, -0.215021, 0.701563}, +{-0.498649, 0.476058, 0.724374}, +{0.0845595, 0.642359, 0.761724}, +{0.610932, 0.694006, 0.380943}, +{-0.562279, -0.393826, 0.727148}, +{-0.309404, -0.488311, 0.815979}, +{0.885899, 0.0837269, 0.45626}, +{-0.0971775, -0.363921, -0.926347}, +{0.409523, 0.889623, -0.202143}, +{0.311584, -0.57582, -0.755875}, +{0.587849, -0.802894, 0.0989699}, +{0.795965, -0.532648, 0.287621}, +{-0.846199, -0.189443, 0.498054}, +{0.695243, -0.652536, 0.301387}, +{0.628055, 0.274577, 0.728117}, +{-0.576019, 0.688255, -0.44103}, +{0.738818, 0.54163, 0.400981}, +{0.664963, 0.321857, 0.673968}, +{0.0624466, 0.411091, -0.909453}, +{0.530934, -0.555077, -0.640312}, +{-0.384098, 0.420588, -0.821933}, +{0.0875831, 0.72359, -0.68465}, +{-0.994236, 0.0967291, 0.046251}, +{0.146737, -0.806988, -0.572048}, +{0.137215, -0.273842, -0.951936}, +{0.836988, 0.53746, -0.1029}, +{0.320899, -0.0780192, -0.943894}, +{-0.555955, -0.815488, -0.160916}, +{-0.300561, 0.8539, 0.424874}, +{0.65946, 0.700367, 0.273128}, +{0.168787, -0.922117, 0.348154}, +{0.981677, 0.0347978, 0.187347}, +{0.439493, 0.0806838, -0.894615}, +{-0.941232, 0.0338837, -0.336056}, +{0.902056, 0.240554, -0.358368}, +{-0.532769, -0.00974262, 0.846205}, +{0.313342, -0.741408, -0.593407}, +{0.772939, -0.130526, -0.620909}, +{0.351409, 0.628187, -0.694185}, +{-0.613224, -0.278286, 0.739265}, +{0.493252, 0.461993, -0.737065}, +{0.468243, 0.351733, -0.810575}, +{-0.121265, 0.967492, 0.221934}, +{-0.0418965, 0.997999, 0.0473483}, +{0.714151, 0.419431, -0.560416}, +{0.92816, 0.273364, -0.252568}, +{-0.387882, -0.0986398, 0.916416}, +{0.454915, 0.82352, 0.33892}, +{0.939215, -0.29534, -0.175071}, +{-0.110855, 0.350433, 0.930004}, +{-0.795868, -0.603359, 0.0505148}, +{0.437998, -0.104108, -0.892927}, +{0.660449, -0.290846, -0.692254}, +{0.707355, 0.678726, 0.197432}, +{0.187352, -0.953774, -0.234978}, +{0.236054, 0.888549, -0.393395}, +{-0.562473, -0.687097, 0.459915}, +{0.116649, -0.905524, -0.407946}, +{-0.00375796, -0.954848, -0.297071}, +{0.969646, 0.234324, -0.0698487}, +{0.611054, -0.706184, -0.357655}, +{-0.405953, 0.648746, -0.643686}, +{-0.382589, -0.489329, 0.783698}, +{0.720065, -0.239119, 0.651405}, +{0.532524, 0.841165, 0.0941273}, +{0.629624, -0.741271, -0.232576}, +{0.636311, 0.4287, -0.641346}, +{0.752229, 0.44629, -0.484744}, +{-0.447495, 0.866523, 0.221102}, +{-0.807279, -0.0925687, -0.582865}, +{-0.202069, -0.921704, -0.331104}, +{-0.0879049, -0.702763, -0.705972}, +{-0.416006, -0.83348, -0.363663}, +{0.149587, -0.950857, -0.271099}, +{0.58252, 0.797993, -0.154526}, +{0.185037, -0.881507, -0.434404}, +{-0.186892, 0.576758, -0.795249}, +{0.768275, -0.31828, 0.555385}, +{-0.341624, -0.638628, -0.689527}, +{-0.481821, -0.557437, 0.676101}, +{0.884766, -0.382344, 0.266464}, +{0.162069, -0.911581, 0.377827}, +{0.634314, 0.0535449, 0.771219}, +{-0.453231, -0.877817, 0.154979}, +{-0.587283, 0.64944, -0.483038}, +{-0.786874, -0.247218, 0.565432}, +{-0.742553, -0.657052, 0.129992}, +{-0.305286, -0.337529, -0.890435}, +{0.0222397, 0.396906, -0.91759}, +{-0.738873, 0.622373, -0.258301}, +{0.324064, 0.367792, -0.871614}, +{0.171476, 0.590216, -0.788823}, +{-0.818034, -0.111902, 0.564179}, +{-0.443639, -0.762316, -0.471231}, +{-0.432283, 0.834107, 0.342631}, +{0.548462, 0.740889, -0.387651}, +{0.523562, 0.116192, 0.844027}, +{-0.497542, 0.660264, -0.562586}, +{0.693397, 0.67742, 0.245564}, +{-0.554347, 0.34331, 0.75818}, +{-0.663835, -0.676708, 0.318417}, +{0.340256, 0.919351, -0.197534}, +{0.221527, -0.292645, 0.930207}, +{-0.230042, -0.639, 0.734002}, +{0.875182, -0.0870732, 0.475894}, +{0.62755, 0.717855, 0.301438}, +{0.609316, -0.0899034, 0.787815}, +{0.0483973, 0.401984, 0.914367}, +{0.191491, -0.8733, 0.447971}, +{0.947633, -0.0344134, 0.317502}, +{-0.940824, 0.111838, 0.319911}, +{0.534045, -0.396984, 0.746458}, +{0.968332, 0.0266654, 0.248238}, +{0.0378821, 0.956607, -0.288909}, +{0.516663, 0.856179, -0.00401514}, +{0.429393, 0.901735, -0.0499631}, +{-0.638202, 0.713038, -0.290301}, +{0.544462, -0.449368, -0.708258}, +{-0.877041, -0.303294, 0.372575}, +{-0.771237, 0.57971, -0.262925}, +{-0.556925, -0.812225, -0.173563}, +{-0.138957, -0.566267, 0.812424}, +{-0.46577, -0.492698, 0.735056}, +{-0.152617, 0.922742, -0.353915}, +{-0.444039, 0.380425, -0.811237}, +{0.356378, 0.788901, -0.500629}, +{-0.324088, -0.218873, 0.920359}, +{-0.150415, -0.852808, -0.500095}, +{-0.962593, -0.176461, -0.205613}, +{0.64903, -0.528772, 0.546955}, +{-0.701458, -0.495026, -0.512743}, +{-0.202469, -0.0319239, -0.978768}, +{-0.818841, 0.537429, 0.201667}, +{-0.479134, -0.361506, 0.79984}, +{-0.524509, 0.268457, -0.807974}, +{-0.702082, 0.693575, 0.161352}, +{0.348675, 0.852137, -0.390242}, +{0.0966414, 0.318672, -0.942926}, +{-0.617737, 0.672782, -0.407143}, +{0.296083, 0.709182, 0.639841}, +{0.625446, 0.731504, -0.271512}, +{-0.51068, -0.364191, -0.778827}, +{-0.818396, -0.414511, -0.398006}, +{-0.106025, 0.750162, 0.652699}, +{-0.916904, -0.358079, 0.176257}, +{-0.0762473, -0.988151, -0.133203}, +{-0.333817, 0.697019, -0.634611}, +{0.678003, 0.729539, -0.0899111}, +{0.333797, 0.661557, -0.671507}, +{-0.379749, 0.913583, 0.145454}, +{0.23032, 0.696227, -0.679868}, +{0.323088, -0.538924, -0.77793}, +{0.111239, 0.341127, 0.933412}, +{0.877555, 0.461567, 0.129819}, +{-0.906009, 0.340158, -0.251873}, +{-0.34641, -0.391311, -0.85257}, +{-0.600364, -0.170214, 0.781403}, +{-0.810065, -0.43604, 0.392001}, +{0.338285, 0.762735, 0.551178}, +{-0.841061, 0.540441, 0.0232163}, +{-0.712011, 0.15868, -0.684003}, +{-0.804979, 0.391932, 0.445418}, +{-0.644414, -0.075504, 0.76094}, +{0.353098, 0.124598, 0.927252}, +{0.575693, 0.184434, -0.796594}, +{-0.618925, -0.426639, -0.659477}, +{0.72232, -0.00454775, 0.691544}, +{-0.493034, -0.674739, -0.549222}, +{-0.84141, 0.236133, 0.486077}, +{-0.730307, -0.468941, 0.496736}, +{-0.847003, 0.143774, 0.511777}, +{0.219405, 0.770731, 0.598193}, +{-0.262571, 0.905015, 0.334669}, +{0.458735, -0.67818, 0.574137}, +{-0.420987, -0.119881, -0.89911}, +{0.00494382, -0.159686, 0.987155}, +{-0.654871, 0.702545, 0.278521}, +{-0.806687, 0.169039, 0.566287}, +{-0.710681, 0.114586, 0.69412}, +{-0.0273517, 0.00150442, 0.999625}, +{-0.717627, 0.521908, 0.461111}, +{0.766014, -0.051304, -0.640774}, +{-0.478723, 0.514035, 0.711753}, +{-0.136849, -0.316753, 0.938584}, +{0.0606409, -0.933425, 0.353611}, +{0.970236, -0.169979, 0.172478}, +{-0.889594, -0.392702, 0.233253} +}; + +print("building\n"); +let ptree = PointTree(points); +print("points size is " + points.size() + "\n"); + +// { +// use io; +// ofstream f = ofstream("/tmp/point_tree_struct.muc"); +// serialize(f, ptree); +// f.close(); +// } + + +for_each (p; points) +{ + let indices = ptree.intersect(p, .25); +} diff --git a/src/bin/mu/mu-interp/test/qsort.mu b/src/bin/mu/mu-interp/test/qsort.mu new file mode 100644 index 000000000..6085d4704 --- /dev/null +++ b/src/bin/mu/mu-interp/test/qsort.mu @@ -0,0 +1,1287 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +use runtime; + +// +// Quicksort implementation for a string array. Using operators >= +// and <= it should be possible to make a template out of +// this. Notice the use of "let" for implicit variable declarations +// -- no types are used except in the parameter list +// + +operator: <= (bool; string a, string b) { compare(a, b) <= 0; } +operator: >= (bool; string a, string b) { compare(a, b) >= 0; } + +function: sort (void; string[] array) +{ + function: qsort (void; string[] a, int lo, int hi) + { + if (lo < hi) + { + let l = lo, + h = hi, + p = a[hi]; + + do + { + while (l < h && a[l] <= p) l++; + while (h > l && a[h] >= p) h--; + + if (l < h) + { + let t = a[l]; + a[l] = a[h]; + a[h] = t; + } + + } while (l < h); + + let t = a[l]; + a[l] = a[hi]; + a[hi] = t; + + qsort(a, lo, l-1); + qsort(a, l+1, hi); + } + } + + qsort(array, 0, array.size() - 1); +} + + +string[] stuff = { + "Quicksort", "implementation", "for", "a", "string", "array.", "Using", + "operators", ">=", "and", "<=", "it", "should", "be", "possible", "to", + "make", "a", "template", "out", "of", "this.", "Notice", "the", "use", + "of", "let", "for", "implicit", "variable", "declarations", "--", "no", + "types", "are", "used", "except", "in", "the", "parameter", "list" +}; + +sort(stuff); +stuff; + +vec := vector float[3]; + +vec[] moments = +{{8.7023, 7.06972, 1.64906}, + {6.81871, 3.89578, 2.94354}, + {5.34212, 4.9666, 0.388419}, + {1.68641, 1.29225, 0.398533}, + {2.32439, 1.76772, 0.56857}, + {4.03972, 3.047, 1.01001}, + {1.41587, 0.991025, 0.426468}, + {4.96533, 4.07921, 0.901708}, + {2.01383, 1.68809, 0.336155}, + {14.8754, 13.261, 1.64168}, + {2.61679, 1.6814, 0.946569}, + {5.5199, 4.59876, 0.935887}, + {1.10547, 0.78145, 0.328331}, + {3.1557, 2.72234, 0.440412}, + {3.94633, 2.66412, 1.29512}, + {41.9793, 36.7255, 5.38823}, + {0.819927, 0.681097, 0.144874}, + {9.96039, 9.22346, 0.765184}, + {0.644422, 0.545056, 0.105757}, + {0.427979, 0.341007, 0.0953253}, + {0.791876, 0.694295, 0.107655}, + {3.3577, 2.42693, 0.938218}, + {4.27098, 2.72564, 1.55893}, + {7.45851, 6.25508, 1.22046}, + {4.66624, 3.77317, 0.918276}, + {2.87306, 1.87593, 1.00858}, + {4.03539, 2.46554, 1.58539}, + {3.17571, 2.79673, 0.392645}, + {2.17311, 1.72278, 0.463507}, + {66.1572, 58.6335, 7.80213}, + {1.964, 1.80527, 0.171146}, + {0.792976, 0.616736, 0.186575}, + {1.23774, 1.02993, 0.209071}, + {5.95114, 4.40477, 1.56025}, + {0.175257, 0.139833, 0.0432943}, + {0.0574509, 0.0490844, 0.0190225}, + {0.0215548, 0.0161144, 0.0136227}, + {0.303557, 0.194799, 0.114732}, + {0.701038, 0.400971, 0.308524}, + {3.21627, 2.8342, 0.396194}, + {1.2415, 0.886332, 0.358584}, + {1.53491, 1.27373, 0.262966}, + {0.13925, 0.121358, 0.0291563}, + {1.71187, 1.26202, 0.455251}, + {0.191824, 0.137912, 0.0641989}, + {3.28961, 3.00226, 0.294203}, + {0.137347, 0.127291, 0.0165758}, + {0.0341305, 0.02583, 0.00865923}, + {0.184936, 0.132354, 0.0577224}, + {0.0148341, 0.011131, 0.00381995}, + {78.9685, 65.6262, 13.6121}, + {8.30332, 7.5852, 0.728047}, + {2.02143, 1.26372, 0.758972}, + {8.25697, 6.12004, 2.15812}, + {11.8775, 10.5416, 1.37852}, + {2.23109, 1.22954, 1.00436}, + {1.82231, 1.47851, 0.351275}, + {3.55068, 2.40831, 1.15918}, + {2.01339, 1.56462, 0.455067}, + {3.57766, 2.64279, 0.947718}, + {4.33655, 3.70316, 0.641087}, + {0.750945, 0.550373, 0.201362}, + {2.52745, 1.87435, 0.664201}, + {1.72091, 1.33032, 0.396806}, + {4.31047, 3.43582, 0.893038}, + {4.18716, 3.1711, 1.02924}, + {0.346549, 0.277988, 0.0764624}, + {0.0885306, 0.085884, 0.0132869}, + {0.128123, 0.11748, 0.0235188}, + {0.0580553, 0.0494376, 0.0195967}, + {0.526714, 0.388822, 0.14862}, + {0.070431, 0.0570376, 0.0180976}, + {0.0486928, 0.0304703, 0.0226123}, + {0.0590084, 0.0337986, 0.0302258}, + {0.196747, 0.119935, 0.0847218}, + {0.0211301, 0.0189456, 0.0121798}, + {0.188427, 0.131366, 0.0636164}, + {0.396326, 0.349935, 0.0536392}, + {1.55725, 1.17017, 0.390452}, + {0.00782764, 0.00559518, 0.00227068}, + {0.0831844, 0.0479696, 0.0371377}, + {1.08962, 1.02562, 0.0678869}, + {0.103539, 0.0866999, 0.0221294}, + {0.180549, 0.147427, 0.0419115}, + {0.0298299, 0.0282869, 0.0084506}, + {0.106893, 0.0836012, 0.0292096}, + {0.00836091, 0.00634754, 0.00317021}, + {52.691, 39.497, 13.3428}, + {1.9202, 1.55966, 0.371487}, + {4.11983, 3.10639, 1.02365}, + {1, 1, 1}, + {0.0531447, 0.0410361, 0.0158826}, + {25.8407, 20.652, 5.24053}, + {15.6478, 14.1166, 1.57473}, + {1.10867, 0.961559, 0.157158}, + {2.11707, 1.31653, 0.808103}, + {2.85533, 2.44284, 0.417172}, + {0.875837, 0.667718, 0.209474}, + {1.83465, 1.41154, 0.431546}, + {1.62876, 1.51683, 0.122125}, + {0.717239, 0.429048, 0.288362}, + {3.74937, 2.12151, 1.64378}, + {12.7105, 7.65043, 5.08402}, + {2.92803, 2.30918, 0.630292}, + {5.90802, 4.78511, 1.14237}, + {0.407756, 0.331435, 0.0851226}, + {1.5638, 0.998875, 0.572226}, + {5.31801, 3.04181, 2.29475}, + {0.242392, 0.187656, 0.0645136}, + {0.286379, 0.250295, 0.0445785}, + {2.7031, 2.09742, 0.619594}, + {0.021697, 0.0184783, 0.0119435}, + {0.172461, 0.103796, 0.075183}, + {0.178747, 0.123199, 0.0653797}, + {0.0086669, 0.00753896, 0.00573674}, + {0.020422, 0.012441, 0.0109823}, + {0.594537, 0.542211, 0.0602675}, + {0.131776, 0.0860452, 0.0486026}, + {0.0294706, 0.0204203, 0.0114093}, + {0.00842309, 0.00565043, 0.00277958}, + {0.516975, 0.443739, 0.0787254}, + {0.0210645, 0.016192, 0.00499939}, + {0.239994, 0.17644, 0.0706546}, + {0.124404, 0.118693, 0.00695446}, + {0.636053, 0.512404, 0.130572}, + {0.229289, 0.154458, 0.0813455}, + {0.249717, 0.233679, 0.0214508}, + {1.40954, 1.00986, 0.405507}, + {8.88968, 5.69599, 3.21474}, + {22.3651, 19.8782, 2.51303}, + {51.6367, 42.366, 9.46318}, + {0.700816, 0.480275, 0.230073}, + {0.441973, 0.415664, 0.0349221}, + {10.7355, 10.0909, 0.662052}, + {8.98716, 7.37367, 1.63191}, + {0.791966, 0.584332, 0.213041}, + {5.88174, 4.86063, 1.02749}, + {2.75423, 1.53321, 1.2362}, + {1.77215, 1.26756, 0.510577}, + {5.10407, 4.2618, 0.858062}, + {2.0146, 1.46373, 0.563247}, + {5.84938, 3.8451, 2.02256}, + {5.14543, 4.81455, 0.336173}, + {4.98391, 3.78579, 1.21419}, + {1.11112, 1.05023, 0.0684385}, + {3.01869, 2.80753, 0.222249}, + {0.592164, 0.535434, 0.0652983}, + {0.913762, 0.773088, 0.144414}, + {0.14602, 0.105084, 0.0496133}, + {2.72672, 2.49147, 0.243168}, + {1.17675, 0.987944, 0.204032}, + {0.202488, 0.160946, 0.0486666}, + {0.0806817, 0.0552082, 0.0351925}, + {0.189525, 0.147875, 0.0469266}, + {0.217347, 0.143792, 0.0806312}, + {0.023549, 0.0204524, 0.00337413}, + {0.033202, 0.0277514, 0.00599353}, + {8.97891, 5.94694, 3.04935}, + {11.7049, 10.9683, 0.749405}, + {0.0477025, 0.0443888, 0.011272}, + {7.34925, 6.05581, 1.31064}, + {8.85521, 7.0035, 1.86864}, + {7.14529, 4.52502, 2.63515}, + {8.22969, 6.30528, 1.93866}, + {4.05263, 3.48274, 0.584038}, + {3.60122, 2.03261, 1.58015}, + {5.3174, 4.41968, 0.907619}, + {12.8932, 10.146, 2.76872}, + {4.64293, 4.27078, 0.382279}, + {5.99364, 4.44143, 1.56782}, + {4.01796, 3.83853, 0.181572}, + {2.90883, 2.34837, 0.571756}, + {0.908256, 0.855524, 0.0623414}, + {1.43647, 1.08242, 0.35558}, + {1.41902, 1.16535, 0.260221}, + {0.0361697, 0.0308244, 0.0169505}, + {0.0761961, 0.0491315, 0.0330256}, + {0.44291, 0.249025, 0.201812}, + {0.00947471, 0.00780205, 0.00279569}, + {2.00341, 1.85685, 0.153488}, + {0.114191, 0.0910818, 0.0290701}, + {0.123516, 0.0854844, 0.0466912}, + {8.1867, 6.90432, 1.30138}, + {0.264707, 0.230593, 0.043302}, + {0.0213449, 0.0198238, 0.0105788}, + {0.00953348, 0.00805058, 0.00151474}, + {0.16265, 0.121848, 0.0459956}, + {6.76495, 5.75586, 1.02425}, + {5.4974, 3.77142, 1.73695}, + {4.26095, 2.77133, 1.50608}, + {1.08195, 1.01953, 0.0702297}, + {0.0559309, 0.052241, 0.0133558}, + {2.61344, 1.81973, 0.808104}, + {23.8814, 19.808, 4.15065}, + {2.97202, 1.99731, 0.98539}, + {2.06963, 1.82659, 0.251403}, + {1.06737, 0.695679, 0.383574}, + {2.65738, 2.27017, 0.398443}, + {2.31967, 1.65437, 0.680261}, + {7.14229, 6.63868, 0.518849}, + {1.25876, 1.17261, 0.0939557}, + {2.55591, 2.03319, 0.531915}, + {0.381195, 0.347768, 0.0428842}, + {0.223221, 0.197714, 0.0332464}, + {0.0170229, 0.0130729, 0.00972739}, + {0.244836, 0.193189, 0.0598599}, + {0.0643166, 0.0570287, 0.0150811}, + {0.0714372, 0.0513507, 0.0238165}, + {0.196595, 0.157321, 0.04513}, + {25.4225, 20.1074, 5.37356}, + {14.1102, 11.4684, 2.66143}, + {8.9079, 7.05568, 1.87767}, + {3.21417, 2.55072, 0.678552}, + {2.80877, 2.27862, 0.542313}, + {1.09194, 0.677103, 0.419779}, + {8.70194, 7.04845, 1.66918}, + {2.49535, 1.69423, 0.814466}, + {1.95201, 1.38818, 0.573847}, + {2.15524, 1.91625, 0.249915}, + {2.01243, 1.74889, 0.276339}, + {0.269453, 0.226601, 0.0519998}, + {1.21785, 1.12398, 0.100593}, + {0.360251, 0.259478, 0.108781}, + {0.0608174, 0.0385359, 0.030021}, + {0.0500098, 0.0332677, 0.0246232}, + {5.2983, 3.31187, 2.00354}, + {17.2779, 12.7202, 4.58594}, + {23.474, 20.9663, 8.31374}, + {0.272662, 0.251397, 0.0305363}, + {3.90371, 3.28623, 0.630593}, + {0.211855, 0.181503, 0.0413386}, + {3.78401, 3.2956, 0.678457}, + {1.13826, 0.824861, 0.321956}, + {0.62675, 0.565755, 0.0678274}, + {2.291, 2.13453, 0.176007}, + {0.913269, 0.860782, 0.18087}, + {0.0882421, 0.0533898, 0.0406985}, + {0.127477, 0.0853961, 0.0451186}, + {0.217346, 0.179961, 0.0473394}, + {0.0714812, 0.0546231, 0.0259256}, + {0.0587721, 0.0331218, 0.0311193}, + {1.11542, 0.996716, 0.153206}, + {0.206484, 0.183882, 0.0273696}, + {1, 1, 1}, + {0.132891, 0.0777491, 0.0603529}, + {0.0657693, 0.0600469, 0.0127888}, + {1.69332, 1.30075, 0.397334}, + {0.0474517, 0.0403585, 0.0199319}, + {0.197368, 0.17554, 0.0309499}, + {0.44859, 0.395219, 0.0584841}, + {0.14498, 0.0910238, 0.0627984}, + {17.8749, 14.6707, 6.18028}, + {4.793, 4.19759, 3.1013}, + {11.1511, 7.73706, 3.43807}, + {9.22695, 7.11874, 2.12785}, + {1.94488, 1.31073, 0.647991}, + {0.021661, 0.0209841, 0.00123231}, + {1.15155, 0.888397, 0.265072}, + {5.34923, 4.86407, 0.518841}, + {4.78374, 4.02917, 0.773284}, + {0.073328, 0.0680522, 0.0101378}, + {0.774345, 0.6163, 0.167395}, + {1.91164, 1.71274, 0.690014}, + {7.58782, 6.302, 1.29557}, + {15.1908, 13.5817, 1.71262}, + {1.23575, 1.18241, 0.634592}, + {0.284826, 0.226033, 0.06035}, + {0.0669499, 0.0380759, 0.0309611}, + {0.0809768, 0.0638168, 0.0206415}, + {0.0804677, 0.0654487, 0.0176415}, + {0.156838, 0.127298, 0.0312764}, + {0.457608, 0.433963, 0.0304274}, + {0.180229, 0.127873, 0.0584728}, + {0.109513, 0.0915036, 0.0245198}, + {0.081171, 0.0762037, 0.0140752}, + {0.145054, 0.124412, 0.0494969}, + {0.0382808, 0.0359169, 0.00434206}, + {0.0277157, 0.0253443, 0.00261249}, + {0.139802, 0.0869728, 0.0547533}, + {0.254422, 0.249377, 0.0101155}, + {10.1441, 9.63577, 1.00794}, + {17.9848, 15.0084, 3.14873}, + {8.77653, 8.34694, 0.641095}, + {25.3135, 23.8688, 6.74206}, + {2.22135, 2.05712, 0.225782}, + {1.29452, 0.715658, 0.58135}, + {4.72797, 3.55495, 1.69219}, + {0.286602, 0.261308, 0.062582}, + {0.25028, 0.238033, 0.0219496}, + {0.181391, 0.13013, 0.0596726}, + {2.86, 2.63692, 0.484151}, + {0.875629, 0.484786, 0.393902}, + {0.132721, 0.120021, 0.0406144}, + {0.085356, 0.049294, 0.038034}, + {1, 1, 1}, + {1.59089, 1.27281, 0.320894}, + {0.127967, 0.10746, 0.0224391}, + {0.0388999, 0.0358742, 0.00351673}, + {15.5492, 12.0982, 5.99525}, + {2.67492, 2.3436, 0.336763}, + {0.82831, 0.600318, 0.257269}, + {0.763716, 0.685875, 0.0806139}, + {2.05474, 1.66116, 0.441364}, + {0.062088, 0.0610423, 0.0104037}, + {0.0466813, 0.0378355, 0.0131173}, + {0.0270496, 0.0230973, 0.00655321}, + {1.01413, 0.522144, 0.493082}, + {0.246018, 0.195964, 0.0554002}, + {0.0441196, 0.040038, 0.00568142}, + {11.8841, 9.93446, 3.52579}, + {0.835605, 0.79647, 0.0465896}, + {0.572374, 0.549055, 0.0330361}, + {1.95038, 1.77323, 0.181353}, + {0.227013, 0.139513, 0.0900161}, + {0.364488, 0.316335, 0.0539745}, + {2.12416, 1.46348, 1.27554}, + {11.5987, 10.4047, 1.92961}, + {4.42369, 4.2895, 0.503297}, + {0.0799308, 0.079005, 0.0039996}, + {2.9892, 2.33375, 0.928069}, + {3.74921, 3.57704, 0.623184}, + {4.74335, 4.21124, 0.850314}, + {3.75571, 3.25902, 0.996284}, + {12.6058, 11.4796, 2.1161}, + {4.8407, 4.40998, 0.87269}, + {2.7351, 2.36095, 0.987066}, + {0.794144, 0.760597, 0.0360191}, + {0.0389234, 0.0362774, 0.00343047}, + {4.47003, 3.55613, 2.84875}, + {1.18459, 1.09777, 0.0955957}, + {8.78557, 6.70683, 3.26401}, + {1.59802, 1.41253, 0.634017}, + {2.58775, 2.08399, 0.736074}, + {0.720606, 0.500084, 0.413087}, + {2.35259, 2.08589, 0.271368}, + {0.589924, 0.516184, 0.080414}, + {0.0291368, 0.0213166, 0.0114797}, + {14.1128, 11.2719, 4.81783}, + {2.9365, 2.24949, 0.702865}, + {5.588, 4.31723, 1.28931}, + {1.19525, 0.89401, 0.306444}, + {11.1111, 6.57581, 4.71168}, + {0.0309305, 0.0274138, 0.0105801}, + {5.01574, 3.35667, 1.66556}, + {2.26936, 1.57406, 0.713739}, + {0.231835, 0.192993, 0.0454057}, + {1.51526, 1.29792, 1.09701}, + {0.218651, 0.176563, 0.0435878}, + {1.73639, 1.32793, 0.41067}, + {0.0793356, 0.0653715, 0.0286105}, + {0.00833554, 0.00700458, 0.00259731}, + {18.7261, 12.4294, 6.93385}, + {2.30199, 1.6434, 0.674115}, + {7.37312, 4.8932, 2.49833}, + {1.76302, 1.26796, 0.900652}, + {3.46222, 2.71704, 0.759968}, + {1.7858, 1.60474, 0.186332}, + {6.48598, 6.04329, 1.55835}, + {0.0556353, 0.0337006, 0.0246885}, + {3.6868, 2.72282, 0.969563}, + {9.58201, 7.49302, 2.37572}, + {0.743932, 0.652033, 0.0954251}, + {3.66602, 2.98399, 0.686071}, + {0.0406272, 0.0310706, 0.0122747}, + {2.76532, 1.53521, 1.23607}, + {0.632246, 0.403946, 0.229403}, + {0.0241351, 0.0221226, 0.0020211}, + {0.142339, 0.124005, 0.0265078}, + {0.00573276, 0.00518794, 0.000839056}, + {0.0572841, 0.0415831, 0.0243934}, + {0.0341381, 0.0247813, 0.0126582}, + {0.0735212, 0.046598, 0.0288067}, + {0.0238369, 0.0179286, 0.00595357}, + {2.63947, 2.45445, 0.197985}, + {0.375016, 0.324863, 0.0579652}, + {8.81363, 7.33916, 1.50076}, + {23.2981, 20.4433, 8.14062}, + {15.5055, 14.7615, 2.35482}, + {15.3375, 13.7678, 6.53714}, + {2.53572, 2.09912, 0.447013}, + {2.7072, 1.96587, 0.757656}, + {0.273864, 0.256648, 0.0266579}, + {0.768952, 0.662277, 0.113002}, + {0.415715, 0.349034, 0.0689758}, + {0.191984, 0.166416, 0.0278132}, + {0.79202, 0.44085, 0.351541}, + {0.230868, 0.160376, 0.0715315}, + {1.77076, 1.5879, 0.702668}, + {1.24639, 1.02751, 0.221937}, + {0.238487, 0.207159, 0.0331715}, + {0.0013038, 0.000967172, 0.000337649}, + {0.26698, 0.24274, 0.0257794}, + {0.21855, 0.176899, 0.0445332}, + {0.220079, 0.180098, 0.0473948}, + {0.0212353, 0.0201984, 0.00536211}, + {1.43054, 1.3104, 0.123196}, + {0.0588339, 0.0483894, 0.0116207}, + {0.203272, 0.199883, 0.0103986}, + {0.182346, 0.156455, 0.0345913}, + {0.0916478, 0.0620682, 0.0376616}, + {0.34741, 0.30974, 0.0446837}, + {0.0134389, 0.0129615, 0.000484702}, + {7.08185, 5.23785, 1.8606}, + {3.47912, 2.79987, 0.693279}, + {1.03765, 0.67081, 0.370706}, + {6.88691, 5.98944, 0.907286}, + {1.66505, 1.20218, 0.469889}, + {3.76961, 2.56902, 1.21621}, + {2.91935, 2.656, 0.274326}, + {2.43954, 1.60399, 0.845691}, + {14.9017, 12.2302, 4.54332}, + {2.07764, 1.80323, 0.283034}, + {0.0437717, 0.0371563, 0.0134892}, + {12.1478, 9.73656, 3.97148}, + {4.03758, 2.24373, 1.80824}, + {5.34745, 3.43171, 1.9326}, + {11.252, 9.57656, 1.69771}, + {0.127286, 0.116968, 0.0176438}, + {0.00988399, 0.00589432, 0.00400971}, + {0.0143315, 0.0124275, 0.00249178}, + {6.32718, 5.71774, 0.641522}, + {0.0657697, 0.0468823, 0.0343137}, + {0.192698, 0.147752, 0.051647}, + {0.086311, 0.0597662, 0.0304362}, + {0.76989, 0.680461, 0.0978231}, + {0.127309, 0.100518, 0.0350677}, + {0.0242923, 0.0195375, 0.0152219}, + {0.0513385, 0.042936, 0.0122679}, + {0.0121369, 0.0117932, 0.00374247}, + {0.575613, 0.53119, 0.0538131}, + {19.6992, 14.4828, 5.24425}, + {2.2075, 2.02781, 0.183214}, + {2.69354, 2.03072, 0.678333}, + {4.35325, 3.94544, 0.420298}, + {13.0945, 10.4346, 2.68181}, + {5.38769, 4.4608, 0.943999}, + {6.43103, 5.59341, 0.84891}, + {0.443929, 0.385389, 0.0668848}, + {2.44109, 2.02905, 0.413961}, + {3.03029, 2.25479, 0.784773}, + {0.915234, 0.688669, 0.228817}, + {0.285928, 0.191844, 0.101265}, + {0.184856, 0.146205, 0.0434331}, + {2.06544, 1.59884, 0.476522}, + {1.21298, 0.951662, 0.265789}, + {0.152357, 0.100482, 0.0602664}, + {1.49674, 0.955703, 0.542666}, + {1.52456, 0.812132, 0.725285}, + {0.141447, 0.0890405, 0.0605293}, + {1.0558, 0.792375, 0.267011}, + {3.51543, 2.70293, 0.82646}, + {1.76908, 1.14052, 0.633078}, + {35.8092, 29.1864, 6.72867}, + {0.175993, 0.101234, 0.0837356}, + {8.95415, 5.73644, 3.23695}, + {5.06713, 3.81823, 1.26387}, + {2.25882, 1.49925, 0.771402}, + {4.86292, 3.58665, 1.29162}, + {0.771316, 0.693304, 0.0866308}, + {1.01993, 0.638366, 0.38214}, + {0.254174, 0.191948, 0.0703883}, + {0.77549, 0.612471, 0.167778}, + {0.54124, 0.510826, 0.0389143}, + {0.199602, 0.140572, 0.0677057}, + {0.287455, 0.242148, 0.0526442}, + {0.173996, 0.130082, 0.0493511}, + {0.0333242, 0.0315734, 0.0035772}, + {0.0102486, 0.00910416, 0.00396866}, + {0.0242143, 0.0207167, 0.0062452}, + {0.00649443, 0.00529888, 0.00234322}, + {0.0850115, 0.0649499, 0.0258313}, + {2.44155, 1.30392, 1.14776}, + {3.44845, 2.55322, 0.908125}, + {15.6855, 14.1869, 1.53252}, + {1.45466, 1.13895, 0.32485}, + {1.33597, 1.20342, 0.14046}, + {21.4269, 16.2354, 5.22591}, + {1.17277, 0.886283, 0.298237}, + {3.48276, 2.1434, 1.35177}, + {3.78956, 2.8878, 0.915971}, + {4.71226, 3.66442, 1.06129}, + {9.28259, 8.03689, 1.26868}, + {1.03358, 0.888357, 0.147817}, + {2.84709, 2.39566, 0.459245}, + {1.50118, 1.1819, 0.324373}, + {6.52103, 5.0374, 1.50347}, + {0.0487626, 0.0423081, 0.0123136}, + {1.01322, 0.892398, 0.129085}, + {0.520948, 0.471449, 0.0564533}, + {0.25816, 0.226806, 0.0400623}, + {0.139755, 0.0916282, 0.0537178}, + {2.39137, 2.22736, 0.17058}, + {1.22082, 1.02655, 0.201332}, + {1.71972, 1.24022, 0.484713}, + {1.43753, 1.1682, 0.272799}, + {0.121352, 0.0844706, 0.0427195}, + {0.0360906, 0.0266797, 0.0115155}, + {0.762576, 0.459468, 0.304355}, + {0.0419409, 0.0302738, 0.0171617}, + {0.112514, 0.0896567, 0.0287224}, + {0.229995, 0.175833, 0.0605142}, + {35.0809, 21.6045, 13.5859}, + {1.99583, 1.51073, 0.497645}, + {5.43906, 5.3264, 0.122219}, + {3.44324, 3.12633, 0.33031}, + {20.08, 16.9176, 3.20894}, + {2.31994, 1.70242, 0.625308}, + {0.600716, 0.541435, 0.0656628}, + {0.109481, 0.0629591, 0.0528276}, + {1.82127, 1.50094, 0.325871}, + {4.74184, 2.55679, 2.19947}, + {4.31017, 2.91072, 1.41503}, + {1.48708, 1.37028, 0.124736}, + {1.26952, 0.995836, 0.27748}, + {10.5397, 8.34826, 2.20935}, + {0.347552, 0.285523, 0.0717659}, + {4.48272, 4.00736, 0.487265}, + {1.0608, 0.587014, 0.473785}, + {4.44283, 2.63441, 1.82329}, + {4.01307, 2.66177, 1.36503}, + {1.42258, 1.1401, 0.289676}, + {2.68953, 2.14082, 0.553768}, + {1.38705, 1.19326, 0.195793}, + {0.361688, 0.2355, 0.132728}, + {0.0370864, 0.0279233, 0.0123706}, + {0.10534, 0.0853222, 0.0275868}, + {0.197504, 0.116806, 0.0865925}, + {0.0449745, 0.0269627, 0.0236144}, + {0.0693139, 0.0453928, 0.0272578}, + {0.0964872, 0.0624707, 0.0394278}, + {0.0888281, 0.066625, 0.0298104}, + {11.179, 9.61399, 1.57863}, + {2.28393, 1.89633, 0.400477}, + {74.3569, 62.425, 12.2598}, + {42.4157, 36.2195, 6.38722}, + {5.5394, 4.73397, 0.820941}, + {6.09445, 4.60318, 1.51072}, + {0.208389, 0.176995, 0.0417469}, + {0.461659, 0.41905, 0.0481028}, + {1.09865, 0.671423, 0.431751}, + {1.42836, 1.16665, 0.263003}, + {0.0539742, 0.037883, 0.0249362}, + {0.337135, 0.297758, 0.0511744}, + {1.39801, 1.02258, 0.379456}, + {2.30964, 1.81131, 0.511783}, + {13.2374, 10.9858, 2.28156}, + {0.035162, 0.0277973, 0.0214631}, + {5.4459, 5.07724, 0.388071}, + {1.00295, 0.86908, 0.13568}, + {0.0301763, 0.0265817, 0.0118823}, + {0.411857, 0.342346, 0.0796562}, + {1.72607, 1.47433, 0.264186}, + {0.0980293, 0.0795219, 0.0262239}, + {0.0292159, 0.0268622, 0.0165541}, + {0.245528, 0.198905, 0.0576227}, + {0.138509, 0.088503, 0.0565466}, + {0.356466, 0.295885, 0.0673612}, + {0.726384, 0.640637, 0.0937758}, + {0.545755, 0.493287, 0.0587876}, + {0.423818, 0.35077, 0.080433}, + {0.0270096, 0.0217448, 0.0094005}, + {0.462356, 0.417986, 0.0550771}, + {0.0556968, 0.0344736, 0.0297585}, + {1, 1, 1}, + {0.131364, 0.106232, 0.0319777}, + {0.404457, 0.316964, 0.0928353}, + {0.0988182, 0.0711878, 0.0401297}, + {0.450172, 0.369768, 0.0865525}, + {0.109975, 0.0799693, 0.0426264}, + {0.052028, 0.0504882, 0.00184827}, + {0.24606, 0.181613, 0.0708531}, + {0.0882236, 0.0700878, 0.0288089}, + {7.50316, 4.57435, 2.94583}, + {9.91907, 6.47692, 3.47032}, + {33.001, 29.5434, 3.56981}, + {1.89015, 1.43139, 0.468398}, + {0.936056, 0.778478, 0.160542}, + {4.42507, 3.50493, 0.934906}, + {19.701, 16.4531, 3.28699}, + {9.12168, 6.78954, 2.3522}, + {29.9185, 27.1376, 2.85034}, + {2.09051, 1.68418, 0.416682}, + {8.35081, 6.00367, 2.37512}, + {1.86215, 1.14593, 0.725212}, + {8.66164, 5.73657, 2.94812}, + {1.99325, 1.6342, 0.366323}, + {1.15332, 0.671881, 0.481887}, + {3.47433, 3.17088, 0.315988}, + {1.35787, 0.973226, 0.386209}, + {1.55523, 1.23368, 0.33342}, + {2.91536, 2.24817, 0.672042}, + {0.455701, 0.442171, 0.0238304}, + {0.1466, 0.100102, 0.0545385}, + {0.266535, 0.194243, 0.0794989}, + {0.49238, 0.417571, 0.0835695}, + {2.05436, 1.97732, 0.0824321}, + {0.375428, 0.219036, 0.164636}, + {1.16541, 0.751848, 0.422086}, + {0.603989, 0.463992, 0.147009}, + {0.211527, 0.145577, 0.0716838}, + {0.0487339, 0.0351283, 0.0193279}, + {0.0641353, 0.042644, 0.0275488}, + {0.0463357, 0.043714, 0.00295413}, + {0.167203, 0.111307, 0.0603819}, + {0.00461006, 0.00380671, 0.001108}, + {0.221783, 0.209225, 0.0218666}, + {0.100858, 0.082349, 0.0257553}, + {1.06958, 0.971062, 0.104735}, + {0.00552824, 0.00551499, 5.54314e-05}, + {6.19728, 4.89977, 1.3235}, + {1.3829, 1.04546, 0.342909}, + {22.6204, 18.5365, 4.16019}, + {1.35548, 0.92788, 0.434411}, + {4.35327, 2.88394, 1.48878}, + {26.5657, 20.9456, 5.79884}, + {45.703, 35.9361, 9.92093}, + {19.4708, 17.3528, 2.15551}, + {6.70066, 4.67057, 2.04154}, + {2.73389, 1.75777, 0.98089}, + {10.2995, 6.41881, 3.91622}, + {3.34588, 2.46283, 0.898574}, + {0.0854082, 0.0792519, 0.0198318}, + {0.146839, 0.104142, 0.0500185}, + {0.846123, 0.576061, 0.273135}, + {2.02127, 1.78931, 0.243674}, + {0.52923, 0.465827, 0.0719565}, + {1.14926, 0.931385, 0.221417}, + {0.00876382, 0.00656142, 0.00220524}, + {5.13236, 4.58499, 0.552269}, + {0.817247, 0.547092, 0.270834}, + {0.111263, 0.0959666, 0.025888}, + {0.122829, 0.10003, 0.0325416}, + {0.174279, 0.133985, 0.0469757}, + {0.75322, 0.676764, 0.0860469}, + {0.018453, 0.0143592, 0.00410596}, + {1.84044, 0.979063, 0.87582}, + {0.165328, 0.153981, 0.0202271}, + {0.530178, 0.46634, 0.0744077}, + {1.08964, 0.814803, 0.280932}, + {0.013075, 0.0121212, 0.00198102}, + {0.0842785, 0.0585423, 0.0318835}, + {0.0190805, 0.0173416, 0.00352091}, + {0.618541, 0.55855, 0.0666146}, + {0.530215, 0.467494, 0.0706812}, + {0.13173, 0.0778893, 0.0619972}, + {5.69066, 4.85642, 0.85092}, + {0.579403, 0.484699, 0.102072}, + {0.156509, 0.117746, 0.0455711}, + {0.443265, 0.367723, 0.0847998}, + {0.0414372, 0.0329287, 0.0147658}, + {0.165069, 0.0901506, 0.08082}, + {1, 1, 1}, + {2.04905, 1.50906, 0.5493}, + {0.163197, 0.11247, 0.0568895}, + {0.725648, 0.684529, 0.0483323}, + {0.0900834, 0.0724949, 0.0252114}, + {0.362711, 0.314024, 0.0565961}, + {0.000517886, 0.0004645, 6.05109e-05}, + {1.16267, 1.09932, 0.0698627}, + {0.274263, 0.176579, 0.103413}, + {0.0755005, 0.0705284, 0.0103151}, + {4.4762, 3.38298, 1.10998}, + {7.62847, 4.36426, 3.28664}, + {51.0583, 44.0853, 7.1994}, + {1.29142, 1.04752, 0.253757}, + {6.2592, 5.33171, 0.946188}, + {3.10395, 1.943, 1.17214}, + {0.994752, 0.731163, 0.26363}, + {7.53603, 5.30395, 2.25709}, + {2.47336, 1.70304, 0.78106}, + {1.78434, 1.28861, 0.505847}, + {0.126049, 0.0955214, 0.037183}, + {3.78127, 2.74106, 1.06133}, + {1.86729, 1.15299, 0.717866}, + {20.1189, 13.2192, 6.98529}, + {1.66552, 1.2862, 0.390742}, + {1.35589, 1.01965, 0.338444}, + {0.152402, 0.102559, 0.0641751}, + {2.55095, 1.67214, 0.888975}, + {4.59748, 3.50702, 1.10553}, + {1.8361, 1.25536, 0.59143}, + {0.0287627, 0.0228079, 0.0152473}, + {5.58012, 3.9782, 1.61512}, + {7.59918, 4.36599, 3.25741}, + {0.765575, 0.746196, 0.0297025}, + {1.94964, 1.46924, 0.48603}, + {0.791305, 0.613263, 0.179021}, + {1.6696, 1.48717, 0.189311}, + {0.778637, 0.572433, 0.207301}, + {0.115328, 0.0836074, 0.0427354}, + {1.48181, 0.800527, 0.689837}, + {0.257054, 0.153072, 0.111482}, + {0.748635, 0.475696, 0.281437}, + {0.181237, 0.156507, 0.0294724}, + {0.145395, 0.12041, 0.0285291}, + {0.222952, 0.160106, 0.0732393}, + {0.0112412, 0.0107362, 0.000557122}, + {0.0556938, 0.0427189, 0.0243914}, + {0.116943, 0.086711, 0.0395364}, + {0.255105, 0.206623, 0.0571709}, + {0.26995, 0.222305, 0.0551473}, + {0.473994, 0.353518, 0.128505}, + {0.30593, 0.232284, 0.0808484}, + {0.0759935, 0.0647114, 0.0182555}, + {0.0512924, 0.0465853, 0.0112451}, + {0.0359301, 0.0322779, 0.0147424}, + {136.161, 128.231, 9.3981}, + {8.11687, 5.75414, 2.38495}, + {2.58193, 1.33078, 1.26266}, + {13.7114, 10.6808, 3.08197}, + {0.5063, 0.384089, 0.129572}, + {0.381803, 0.322617, 0.0656118}, + {1.1142, 0.868043, 0.247711}, + {1.7535, 1.47612, 0.289231}, + {2.57399, 1.99665, 0.590879}, + {1.61233, 0.986747, 0.633314}, + {10.3613, 9.09235, 1.29957}, + {2.86816, 2.74505, 0.130171}, + {1.68727, 1.54517, 0.147508}, + {1.0077, 0.717342, 0.293536}, + {1.62417, 1.23351, 0.400548}, + {1.96703, 1.27808, 0.703305}, + {0.611618, 0.547065, 0.0718034}, + {1.26624, 1.07287, 0.204474}, + {0.00974127, 0.00779678, 0.00238581}, + {0.0944256, 0.0586319, 0.0390647}, + {0.0553243, 0.0537426, 0.00220193}, + {0.18922, 0.162276, 0.0336891}, + {0.257684, 0.204237, 0.0612973}, + {0.0471742, 0.038731, 0.0145291}, + {1.59759, 0.883053, 0.72624}, + {0.405295, 0.306406, 0.107371}, + {0.857815, 0.634219, 0.226596}, + {0.233612, 0.194749, 0.0487531}, + {1, 1, 1}, + {0.0110239, 0.00905208, 0.00493226}, + {0.290624, 0.219472, 0.0791362}, + {0.492303, 0.413801, 0.0856225}, + {0.0454092, 0.0433167, 0.00551288}, + {0.551403, 0.490786, 0.0671915}, + {0.0272596, 0.0265791, 0.00154649}, + {0.0645803, 0.0399234, 0.030272}, + {0.0261022, 0.0244582, 0.0111724}, + {0.214588, 0.163342, 0.0557665}, + {1.06598, 0.911364, 0.15758}, + {1, 1, 1}, + {0.122814, 0.0928977, 0.0396223}, + {0.17705, 0.122654, 0.0645235}, + {0.23302, 0.160938, 0.0771497}, + {0.18043, 0.133942, 0.0489646}, + {4.00575, 3.00574, 1.01499}, + {21.894, 16.239, 5.71349}, + {0.979108, 0.779032, 0.208019}, + {17.3816, 13.1331, 4.30029}, + {3.07466, 1.94549, 1.14119}, + {18.845, 14.9376, 3.95559}, + {6.08491, 5.21353, 0.8933}, + {1.75154, 1.48178, 0.275958}, + {10.1971, 8.10984, 2.11364}, + {18.064, 14.5715, 3.55341}, + {1.20087, 1.10875, 0.0999008}, + {14.0044, 13.0248, 1.00306}, + {1.02583, 0.629379, 0.401413}, + {6.27762, 5.10072, 1.19494}, + {6.16909, 4.7232, 1.46714}, + {0.674177, 0.591196, 0.0891393}, + {2.94121, 2.21963, 0.726759}, + {0.208765, 0.182356, 0.0326379}, + {1.04223, 0.789592, 0.262313}, + {0.234628, 0.154917, 0.0882879}, + {2.84705, 2.08256, 0.777981}, + {0.528841, 0.326313, 0.209856}, + {2.42735, 1.58654, 0.852499}, + {0.193456, 0.176985, 0.0283737}, + {2.38253, 2.15643, 0.233519}, + {2.62863, 2.26697, 0.371434}, + {0.108773, 0.098265, 0.0191194}, + {1.29584, 1.22649, 0.0762687}, + {1.43863, 1.25676, 0.185036}, + {1.8242, 1.78191, 0.0507334}, + {0.286044, 0.229331, 0.0668729}, + {0.0176376, 0.0162528, 0.00440926}, + {0.180804, 0.127136, 0.0596873}, + {0.349192, 0.259163, 0.0970085}, + {0.417157, 0.368545, 0.0555908}, + {0.0310765, 0.0241573, 0.0156966}, + {0.0431953, 0.0389883, 0.00444264}, + {2.71733, 1.77476, 0.959285}, + {0.135463, 0.0863945, 0.0608286}, + {67.3969, 58.8459, 8.9235}, + {8.05403, 6.71284, 1.36059}, + {18.0621, 12.2924, 5.84296}, + {20.2305, 15.9721, 4.33164}, + {6.4722, 4.88045, 1.60938}, + {10.0293, 8.87657, 1.18077}, + {2.4192, 1.76507, 0.661254}, + {0.250395, 0.172334, 0.0867311}, + {2.89106, 2.41714, 0.488738}, + {2.06286, 1.61433, 0.451863}, + {0.252356, 0.19092, 0.069322}, + {0.412286, 0.23539, 0.18416}, + {1.59784, 1.0346, 0.574241}, + {2.86665, 2.30305, 0.571653}, + {2.22316, 1.57481, 0.652387}, + {0.293442, 0.240366, 0.0613234}, + {5.88885, 4.70271, 1.20045}, + {4.42407, 3.39011, 1.05297}, + {5.2369, 4.56653, 0.692834}, + {1.54274, 0.99018, 0.568271}, + {2.26284, 1.33439, 0.943323}, + {2.24293, 1.40647, 0.850252}, + {0.261391, 0.206905, 0.0618728}, + {0.183196, 0.159047, 0.0336467}, + {2.82074, 1.7173, 1.11862}, + {0.862482, 0.666912, 0.201415}, + {1.715, 1.05567, 0.665508}, + {0.821383, 0.547192, 0.275234}, + {0.816571, 0.567811, 0.254869}, + {0.167079, 0.12539, 0.0490891}, + {0.0241785, 0.015794, 0.0107117}, + {0.211399, 0.167796, 0.0506839}, + {1.70078, 1.35835, 0.344718}, + {0.0930868, 0.0693393, 0.0254286}, + {0.0915386, 0.0546105, 0.0457348}, + {2.06708, 1.4774, 0.594631}, + {0.281053, 0.242242, 0.0457305}, + {0.117874, 0.0875092, 0.0397996}, + {0.742898, 0.45079, 0.293175}, + {12.3094, 9.78153, 2.56496}, + {10.8703, 9.79492, 1.10275}, + {14.1568, 11.5685, 2.62282}, + {13.9531, 7.86908, 6.11694}, + {1.09897, 0.806742, 0.29826}, + {2.89352, 2.65168, 0.255108}, + {1.45098, 0.882985, 0.573003}, + {15.8843, 14.8738, 1.04176}, + {41.0452, 37.4362, 3.74498}, + {0.697115, 0.425753, 0.271674}, + {3.85276, 3.39687, 0.467643}, + {0.835716, 0.763573, 0.079959}, + {3.47732, 3.04648, 0.446039}, + {5.78931, 3.63163, 2.16816}, + {3.28991, 1.84617, 1.46008}, + {2.77806, 2.05002, 0.73814}, + {3.32644, 2.60044, 0.734202}, + {0.943943, 0.659086, 0.285081}, + {1.64058, 1.33628, 0.310639}, + {1.32976, 1.1809, 0.156675}, + {2.49775, 2.20163, 0.309124}, + {1.19089, 0.994476, 0.198862}, + {0.530944, 0.39683, 0.140556}, + {1.00024, 0.70583, 0.304382}, + {1.178, 0.987548, 0.196189}, + {0.215219, 0.165354, 0.0572806}, + {0.198745, 0.122136, 0.0826452}, + {0.069969, 0.0636274, 0.0113907}, + {0.167176, 0.128602, 0.0426937}, + {0.0785269, 0.0586375, 0.0204621}, + {0.0394446, 0.0348608, 0.00683681}, + {0.0426907, 0.0349281, 0.00992754}, + {0.0308461, 0.0289264, 0.00246575}, + {0.420009, 0.348858, 0.0795153}, + {5.10473, 4.1751, 0.946335}, + {3.6807, 2.95122, 0.745367}, + {6.36508, 5.11217, 1.27021}, + {38.5533, 32.1161, 6.59608}, + {18.0371, 12.7263, 5.37337}, + {0.422236, 0.365079, 0.0653846}, + {0.125622, 0.112514, 0.0228402}, + {0.0264293, 0.0241626, 0.005712}, + {14.8777, 13.3761, 1.543}, + {12.9091, 8.95149, 4.00369}, + {20.957, 11.3851, 9.6552}, + {0.84974, 0.584857, 0.26922}, + {2.23701, 1.75327, 0.489204}, + {1.84591, 1.09235, 0.765531}, + {1.2053, 0.852416, 0.358141}, + {0.238852, 0.202883, 0.0424368}, + {11.4029, 10.6303, 0.822242}, + {11.6007, 10.7017, 0.974187}, + {7.83364, 6.58481, 1.27203}, + {11.7808, 10.045, 1.78944}, + {10.7131, 6.86751, 3.88067}, + {0.0595007, 0.0582293, 0.0153646}, + {1.97345, 1.44647, 0.535317}, + {2.1138, 1.76555, 0.357587}, + {5.86235, 5.48419, 0.410571}, + {3.91567, 3.34865, 0.588957}, + {4.00554, 2.92872, 1.09754}, + {0.919977, 0.726307, 0.194729}, + {1.92325, 1.30926, 0.628039}, + {0.0502879, 0.0407002, 0.0178575}, + {0.377813, 0.332533, 0.0528138}, + {2.3509, 1.87786, 0.482082}, + {0.265577, 0.223583, 0.0502328}, + {1.47487, 1.41674, 0.0674176}, + {0.0636665, 0.0461357, 0.0252918}, + {0.136227, 0.0975046, 0.0470931}, + {0.106229, 0.0736141, 0.04056}, + {1, 1, 1}, + {3.04663, 2.30367, 0.760171}, + {0.191084, 0.153099, 0.0451029}, + {3.99483, 3.92968, 0.0754393}, + {1.05337, 0.910342, 0.143422}, + {0.407827, 0.318566, 0.0957831}, + {0.0845008, 0.0585783, 0.0304141}, + {0.159853, 0.101026, 0.0652356}, + {1.2159, 1.0249, 0.191078}, + {0.243114, 0.201407, 0.0515826}, + {1, 1, 1}, + {0.154637, 0.0969592, 0.0666122}, + {0.0876052, 0.0574888, 0.0377768}, + {0.123669, 0.106507, 0.0268009}, + {13.3187, 11.2975, 2.19602}, + {37.7245, 33.1327, 5.72172}, + {8.35385, 5.4695, 2.92998}, + {0.353245, 0.24958, 0.108396}, + {2.00802, 1.29303, 0.729825}, + {0.304298, 0.236685, 0.0721897}, + {0.113444, 0.0955713, 0.0221598}, + {0.146521, 0.117458, 0.0324014}, + {10.3719, 7.86686, 2.6227}, + {11.7484, 10.9855, 0.915566}, + {0.104358, 0.072623, 0.0347595}, + {1.22654, 1.019, 0.218953}, + {2.81386, 1.94857, 0.880716}, + {0.00191448, 0.00181531, 0.000108993}, + {0.299607, 0.236183, 0.0664375}, + {0.437393, 0.265666, 0.176336}, + {3.09853, 2.67361, 0.441364}, + {0.567227, 0.526105, 0.0449571}, + {0.396328, 0.340816, 0.0596217}, + {18.5271, 12.9363, 5.72951}, + {19.692, 17.8447, 2.37161}, + {27.6444, 24.2708, 3.96514}, + {0.2526, 0.169296, 0.0866717}, + {1.00728, 0.591974, 0.41636}, + {0.0499009, 0.0352623, 0.019529}, + {5.46561, 4.68004, 0.818457}, + {1.43257, 1.15827, 0.282974}, + {7.61867, 7.0433, 0.638096}, + {0.480032, 0.447304, 0.0368096}, + {19.8419, 17.5688, 2.58453}, + {0.0218896, 0.015364, 0.00842714}, + {2.53367, 2.30301, 0.245417}, + {0.176769, 0.162179, 0.0184679}, + {0.0137867, 0.0133776, 0.00123578}, + {0.0748581, 0.0716156, 0.00777219}, + {0.0825294, 0.0534727, 0.0324472}, + {0.0750064, 0.055564, 0.02139}, + {15.45, 14.2932, 1.21725}, + {11.503, 8.08291, 3.48959}, + {29.0331, 21.1842, 8.32903}, + {8.23711, 6.92427, 1.49739}, + {1.69285, 1.11027, 0.592038}, + {7.41778, 7.11211, 0.392808}, + {7.09306, 4.82714, 2.31207}, + {0.614229, 0.448118, 0.170853}, + {1.86812, 1.47893, 0.397071}, + {0.423034, 0.284212, 0.145365}, + {0.509647, 0.332287, 0.180667}, + {1.40985, 0.98864, 0.426025}, + {0.681265, 0.496383, 0.189035}, + {0.0312305, 0.0247007, 0.0106941}, + {0.788489, 0.745483, 0.0485609}, + {0.175414, 0.155447, 0.0276586}, + {0.014626, 0.0144271, 0.00358455}, + {0.327379, 0.184054, 0.147505}, + {0.124238, 0.0993218, 0.0305309}, + {0.165443, 0.103642, 0.0671772}, + {0.127133, 0.105909, 0.0252865}, + {0.185848, 0.150708, 0.0397301}, + {0.0436415, 0.0338922, 0.0156298}, + {0.0190524, 0.0134846, 0.0111712}, + {11.6854, 9.72726, 2.12811}, + {4.4832, 4.0202, 0.473543}, + {27.1967, 21.3222, 6.12423}, + {3.4738, 2.7975, 0.687504}, + {13.8131, 9.70678, 4.20136}, + {6.9058, 4.31263, 2.67156}, + {1.16419, 0.888342, 0.28009}, + {10.6303, 8.0694, 2.63044}, + {2.75541, 2.30249, 0.460245}, + {5.53311, 4.23675, 1.32384}, + {0.440195, 0.290426, 0.153361}, + {1.53265, 1.34025, 0.199959}, + {1.31204, 0.93703, 0.377966}, + {0.0321606, 0.0284217, 0.00610622}, + {1.73691, 1.6721, 0.0704116}, + {0.0683522, 0.0640055, 0.00671454}, + {0.166418, 0.107165, 0.0635027}, + {0.0756182, 0.0439616, 0.0353918}, + {0.00768406, 0.00744395, 0.000241552}, + {1.72206, 1.6303, 0.091777}, + {0.0271172, 0.0165194, 0.0145304}, + {0.0473292, 0.0338289, 0.0171597}, + {0.0597149, 0.0357664, 0.0264382}, + {0.276102, 0.218231, 0.0608535}, + {0.0883366, 0.0835118, 0.00506125}, + {7.77482, 5.25132, 2.56523}, + {6.59892, 5.536, 1.12465}, + {28.1128, 21.169, 7.49406}, + {10.328, 8.52408, 1.93289}, + {3.40929, 2.98576, 0.439991}, + {13.3129, 11.0977, 2.37595}, + {12.4738, 8.49042, 4.0457}, + {6.87521, 5.05718, 1.85348}, + {2.8762, 2.09023, 0.804857}, + {16.3208, 14.2036, 2.28444}, + {2.09997, 1.61214, 0.492949}, + {0.161398, 0.0867639, 0.0797961}, + {5.11987, 3.09107, 2.05201}, + {4.62707, 4.47507, 0.18237}, + {4.25373, 2.4158, 1.86211}, + {12.1968, 12.0515, 0.349646}, + {1.66782, 0.980818, 0.692161}, + {0.00882216, 0.00638279, 0.00371673}, + {1.08751, 0.749057, 0.347809}, + {0.0777576, 0.053991, 0.0285628}, + {0.260686, 0.190254, 0.0733815}, + {0.29775, 0.24779, 0.0548651}, + {2.24723, 1.70694, 0.549848}, + {0.191365, 0.114361, 0.0807361}, + {0.0454594, 0.0335128, 0.0142004}, + {1, 1, 1}, + {0.52662, 0.353346, 0.177403}, + {0.114346, 0.0637251, 0.0542928}, + {0.04731, 0.0376979, 0.00961301}, + {0.115142, 0.0853029, 0.0341461}, + {0.00464026, 0.00452752, 0.0013164}, + {0.118471, 0.0874372, 0.0340117}, + {0.203414, 0.173596, 0.0335665}, + {68.3237, 47.5867, 22.7841}, + {27.4579, 20.9759, 6.92349}, + {7.62961, 5.53442, 2.13624}, + {48.5647, 45.7752, 4.58165}, + {18.8214, 14.6882, 4.31853}, + {0.39421, 0.272094, 0.125948}, + {2.41257, 1.90596, 0.511351}, + {2.48869, 2.01601, 0.484734}, + {11.4005, 10.0698, 1.46953}, + {4.81676, 2.96795, 1.86782}, + {1.37745, 1.1482, 0.239792}, + {2.21523, 2.1969, 0.0189794}, + {0.0711153, 0.0489934, 0.0250118}, + {2.44018, 2.39003, 0.0637319}, + {0.004766, 0.00423689, 0.000597409}, + {2.15435, 1.92974, 0.241388}, + {0.82853, 0.669122, 0.163723}, + {1, 1, 1}, + {0.0217144, 0.0150497, 0.00671092}, + {0.116698, 0.0793005, 0.0407661}, + {0.0119949, 0.00920713, 0.00472136}, + {0.531312, 0.418903, 0.11692}, + {0.199165, 0.166835, 0.0362611}, + {0.380405, 0.325505, 0.0586269}, + {1.60148, 1.40147, 0.220155}, + {0.115176, 0.073674, 0.0438985}, + {0.117386, 0.110802, 0.0128604}, + {0.246991, 0.199537, 0.0521135}, + {0.00504716, 0.00386477, 0.00119985}, + {0.0513792, 0.04224, 0.0101446}, + {1, 1, 1}, + {37.5182, 36.2608, 1.34634}, + {0.0182924, 0.013999, 0.00797301}, + {0.00219264, 0.00212958, 0.00071355}, + {4.67936, 3.67758, 1.00522}, + {0.383579, 0.294368, 0.0915444}, + {0.0800879, 0.0665746, 0.0170545}, + {0.253307, 0.18289, 0.0728974}, + {36.7613, 34.2529, 2.61597}, + {0.44922, 0.280552, 0.170688}, + {8.15205, 6.89225, 1.26589}, + {0.00539905, 0.00428019, 0.00332946}, + {1.0315, 0.993008, 0.0411286}, + {0.0175642, 0.0155824, 0.00519473}, + {0.0583745, 0.0384514, 0.0220089}, + {1.06312, 0.953488, 0.111537}, + {39.4152, 36.5971, 2.92233}, + {17.5777, 15.8558, 1.73606}, + {0.010507, 0.00943953, 0.00461835}, + {52.5609, 48.5811, 4.11563}, + {1.95403, 1.72358, 0.232441}, + {0.587594, 0.409354, 0.181002}, + {0.451676, 0.298067, 0.156144}, + {0.275934, 0.220534, 0.0579784}, + {0.0113614, 0.0081534, 0.0053921}, + {0.617316, 0.471927, 0.14659}, + {0.855416, 0.531973, 0.325586}, + {0.00261248, 0.00195033, 0.000770238}, + {0.0438023, 0.0247006, 0.0211761}, + {0.00386285, 0.00382579, 3.97773e-05}, + {25.8855, 20.7696, 6.61177}, + {12.6112, 10.3445, 2.36557}, + {7.09281, 5.11888, 1.99897}, + {12.0571, 9.83813, 2.29124}, + {1.53001, 1.09365, 0.44097}, + {12.2425, 9.91226, 3.48442}, + {1.82423, 1.41972, 0.801297}, + {1.74802, 1.15794, 0.592825}, + {1.57784, 1.29161, 0.287101}, + {1.43226, 0.865538, 0.573474}, + {0.0013157, 0.00111878, 0.000201107}, + {0.120443, 0.0664593, 0.0562211}, + {0.0699373, 0.044475, 0.0273143}, + {0.0153575, 0.0134257, 0.00215565}, + {0.180986, 0.150184, 0.0323547}, + {0.421783, 0.372056, 0.0522688}, + {0.0375231, 0.027402, 0.0117706}, + {1, 1, 1}, + {0.226614, 0.205662, 0.0229068}, + {1, 1, 1}, + {27.3712, 19.9984, 8.63365}, + {0.0510024, 0.0340877, 0.0203693}, + {1.01993, 0.80879, 0.212191}, + {4.62901, 3.76738, 0.89845}, + {0.266826, 0.190075, 0.0796199}, + {2.17625, 1.67406, 0.506289}, + {0.297397, 0.268385, 0.0322036}, + {1.80998, 1.148, 0.665007}, + {1.52514, 1.19214, 0.339763}, + {2.75014, 2.04788, 0.710951}, + {1.70999, 1.26218, 0.780545}, + {1, 1, 1}, + {0.0253182, 0.0239928, 0.00143716}, + {0.40416, 0.357527, 0.0493348}, + {1.05346, 0.994506, 0.0616659}, + {0.220356, 0.154029, 0.068387}, + {0.0932356, 0.0784922, 0.0174922}, + {0.105088, 0.0971614, 0.01166}, + {0.00514165, 0.00496823, 0.000197842}, + {0.287787, 0.237842, 0.0525798}, + {32.7725, 24.1239, 16.7532}, + {27.1496, 23.7912, 6.85192}, + {1.87151, 1.76959, 0.335057}, + {15.1079, 10.8512, 8.29402}, + {2.84323, 1.96247, 0.986192}, + {33.4315, 21.3141, 12.2362}, + {0.140971, 0.125857, 0.0495373}, + {13.7442, 12.8723, 1.82174}, + {3.46683, 2.67331, 0.8377}, + {1.63638, 1.19331, 0.522989}, + {1, 1, 1}, + {12.4225, 9.02347, 3.48307}, + {4.28892, 3.13604, 1.23732}, + {3.61681, 3.08318, 0.630645}, + {0.0294499, 0.0229646, 0.0093859}, + {0.433819, 0.424138, 0.144715}, + {3.82937, 3.06507, 0.833666}, + {0.133922, 0.122457, 0.0450914}, + {0.0144769, 0.0140951, 0.000394247}, + {0.163245, 0.128083, 0.118702}, + {0.247043, 0.214056, 0.079257}, + {0.206672, 0.155376, 0.0793526}, + {0.63769, 0.570037, 0.111278}, + {0.578878, 0.324077, 0.293486}, + {7.03653, 6.36719, 0.753186}, + {0.341023, 0.254797, 0.145619}, + {3.18089, 2.68709, 0.612296}, + {0.1106, 0.0921903, 0.0267943}, + {0.343338, 0.302689, 0.132226}, + {0.0159036, 0.0121168, 0.00498944}, + {0.128986, 0.0840073, 0.067675}, + {0.275703, 0.234426, 0.116008}, + {0.725741, 0.61295, 0.464398}, + {12.0549, 10.823, 4.72117}, + {1.07162, 0.890036, 0.343921}, + {5.91529, 3.56702, 2.43604}, + {26.9427, 17.3261, 9.73347}, + {7.46528, 5.76627, 1.76824}, + {0.879385, 0.746793, 0.136644}, + {4.62393, 2.9604, 2.43569}, + {17.2741, 14.1853, 5.58326}, + {3.29709, 3.1314, 1.02476}, + {5.99291, 3.65511, 3.16417}, + {1.75635, 1.28998, 0.597135}, + {0.835034, 0.707193, 0.517771}, + {10.3385, 6.82211, 3.61996}, + {5.08911, 3.18075, 2.0111}, + {4.57858, 3.27147, 1.78351}, + {1.28975, 0.797295, 0.541994}, + {0.0868908, 0.0801407, 0.0582455}, + {1.63696, 1.03093, 0.658222}, + {13.0903, 8.2374, 4.94005}, + {0.0136103, 0.0133541, 0.000375381}, + {0.0600032, 0.0489908, 0.0253412}, + {0.0633824, 0.0567621, 0.0107549}, + {0.705765, 0.547345, 0.260761}, + {1, 1, 1}, + {0.0135529, 0.013513, 7.21032e-05}, + {3.96979, 2.63031, 1.42488}, + {0.296347, 0.236058, 0.0982237}, + {5.92088, 4.6568, 1.36401}, + {0.0238785, 0.02231, 0.00159379}}; + +string[] lots; +for_each (m; moments) lots.push_back(string(mag(m))); +print("sort first (~N log N)\n"); +sort(lots); + +if (false) +{ + // + // This is a pathalogical case for qsort (the input is already + // sorted) it results in horribly deep recusion since the + // complexity is O(N^2). + // + + try + { + print("sort second N^2\n"); + sort(lots); + } + catch (exception e) + { + print("caught: " + string(e) + "\n"); + print("stack_traits: " + string(stack_traits()) + "\n"); + } +} diff --git a/src/bin/mu/mu-interp/test/qsort_lambda.mu b/src/bin/mu/mu-interp/test/qsort_lambda.mu new file mode 100644 index 000000000..36a8f4d54 --- /dev/null +++ b/src/bin/mu/mu-interp/test/qsort_lambda.mu @@ -0,0 +1,64 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +SortFunc := (int; int, int); + +function: sort (int[]; int[] array, SortFunc comp) +{ + function: qsort (void; int[] a, int lo, int hi, SortFunc comp) + { + if (lo < hi) + { + let l = lo, + h = hi, + p = a[hi]; + + do + { + while (l < h && comp(a[l],p) <= 0) l++; + while (h > l && comp(a[h],p) >= 0) h--; + + if (l < h) + { + let t = a[l]; + a[l] = a[h]; + a[h] = t; + } + + } while (l < h); + + let t = a[l]; + a[l] = a[hi]; + a[hi] = t; + + qsort(a, lo, l-1, comp); + qsort(a, l+1, hi, comp); + } + } + + qsort(array, 0, array.size() - 1, comp); + array; +} + +\: nbits (int; int a) +{ + int count = 0; + for (int i=0; i < 32; i++) if (((a >> i) & 1) == 1) count++; + count; +} + +int[] ebits; +global int[] blookup; + +for (int i=0; i < 100 ; i++) +{ + ebits.push_back(i); + blookup.push_back(nbits(i)); +} + +sort( int[] {2, 3, 7, 4, 1}, \: (int; int a, int b) { a - b; } ); +sort( ebits, \: (int; int a, int b) { blookup[b] - blookup[a]; } ); + diff --git a/src/bin/mu/mu-interp/test/reduction_limits.mu b/src/bin/mu/mu-interp/test/reduction_limits.mu new file mode 100644 index 000000000..974c181b7 --- /dev/null +++ b/src/bin/mu/mu-interp/test/reduction_limits.mu @@ -0,0 +1,60 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// This is testing some difficult aspects of function and expression +// reduction. Reduction here is refering to reduction during the +// parse and/or compile phase not during "run" time. The goal is to +// reduce as much as possible as early as possible. The earlier a +// reduction occurs the faster the parse/compile phase will be +// (because you minimize the number of times you need to scan an +// expression -- at least that's the working theory). +// +// For function reduction, an indirect call must be reduced to a +// direct call. For constant reduction, some functions, namely g +// below, are "maybe pure" and others like f are "pure". maybe pure +// functions are pure only if any function arguments are pure or +// maybe pure. maybe pure functions exist because they are applying +// one of their function arguments. This application is what makes +// the purity of the function unknown. +// +// To see the results you need to run this in the interpreter with +// -printFunc to dump the reduced function. +// + +use math_util; +F := (int; int, int); + +\: e (int a, int b) { seed(a); random(b); } // not pure +\: f (int a, int b) { int(a + b + math.sin(a + b)); } // pure +\: g (F u, int x) { u(u(x, u(x,1)),1); } // maybe pure + +// +// This should reduce to the constant 14 because g is maybe pure, f is +// pure, and the rest in constant. +// + +g(f, f(1,2) + f(2,1)); + +// +// This should never reduce because although e is constant it is not pure +// and since g is maybe pure it cannot be reduced. +// + +g(e, f(1,2) + f(2,1)); // not reduce because e is not pure + +// +// Same exersize, but with non-primitive type +// + +S := (string; string, string); + +\: s (string a, string b) { b + a; } // pure +\: np (string a, string b) { print(""); a; } // not pure +\: t (S u, string x) { u(u(x, u(x, "A")), "B"); } // maybe pure + +t(s, s("E", "F") + s("G", "H")); // reduce to constant "BAFEHGFEHG" +t(np, s("E", "F") + s("G", "H")); // do not reduce (use -print to see if it does) + diff --git a/src/bin/mu/mu-interp/test/regex.mu b/src/bin/mu/mu-interp/test/regex.mu new file mode 100644 index 000000000..50c34a53b --- /dev/null +++ b/src/bin/mu/mu-interp/test/regex.mu @@ -0,0 +1,48 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +\: show (void; string[] pieces) { print("%s\n" % pieces); } + +regex r = "[ab]0"; +print("Regular expression is: " + string(r) + "\n"); + +assert(regex.match(r, "a0") && regex.match(r, "b0")); +assert(regex.match("bar", "foobar")); +assert(!regex.match("yellowberry", "blue")); + +r = regex("A[1-9]", regex.Extended | regex.IgnoreCase); +assert(regex.match(r, "a8") && regex.match(r, "A8")); +assert(r.match("a8") && r.match("A8")); // alternate calling style + +// US telephone number +r = regex("(([0-9]{3})[-.])?([0-9]{3})[-.]([0-9]{4})"); + +assert( int(regex.smatch(r, "555-1234").back()) == 1234 ); + +show( r.smatch("555-1234") ); +show( r.smatch("415-555-1234") ); +show( r.smatch("415.555.1234") ); + +assert(regex.smatch(r, "4015") eq nil); // no match + +{ + let text = "123 bar foo 213 blah 11113", + re = "([12]+)3", + repl = "[\1]", + val = regex.replace(re, text, repl); + + print("text = %s\nre = %s\nrepl = %s\nval = %s\n" % (text, re, repl, val)); + + assert(val == "[12] bar foo [21] blah [1111]"); +} + +print("\n\n\n"); + +let re = regex("(<[^>]+>)([^<]*)(]+>)"); +string te = "123 and more"; + +print("%s\n" % re.smatch(te)); + diff --git a/src/bin/mu/mu-interp/test/relop.mu b/src/bin/mu/mu-interp/test/relop.mu new file mode 100644 index 000000000..3631e9c9f --- /dev/null +++ b/src/bin/mu/mu-interp/test/relop.mu @@ -0,0 +1,44 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +float a = 1.0; +float b = 2.0; + +if (a == b) +{ + assert(false); +} +else +{ + if (a != b) + { + if (a < b) + { + if (a > b) + { + print(0); + } + else + { + if (a <= b && !(a >= b)) + { + print(1); + } + else + { + assert(false); + } + } + } + else + { + assert(false); + } + } + else + { + assert(false); + } +} diff --git a/src/bin/mu/mu-interp/test/require.mu b/src/bin/mu/mu-interp/test/require.mu new file mode 100644 index 000000000..df722bed0 --- /dev/null +++ b/src/bin/mu/mu-interp/test/require.mu @@ -0,0 +1,11 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +//function: foo (int;) { 0; } +//int bar = 10; +// +//require foo; +//use bar; diff --git a/src/bin/mu/mu-interp/test/return.mu b/src/bin/mu/mu-interp/test/return.mu new file mode 100644 index 000000000..a89fc9afb --- /dev/null +++ b/src/bin/mu/mu-interp/test/return.mu @@ -0,0 +1,65 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +function: negabs(int; int x) +{ + if (x > 0) return -x; + return x; +} + +assert( negabs(1) == -1 && negabs(-1) == -1 ); + +function: foobar(string;) +{ + if (true) + { + return "foobar did its thing\n"; + } + else + { + return "problem\n"; + } +} + +print( foobar() ); + + +function: lotsOluck(string;) +{ + for (int i=0; i < 10; i++) + { + if (i == 3) + { + return "returned ok"; + } + } + + assert(false); + return "not so good"; +} + +print( lotsOluck() + "\n" ); + +function: breakAndReturn(int;) +{ + for (int i=0; i < 10; i++) + { + if (i == 2) + { + for (int q=0; q < 10; q++) + { + if (q == 5) return q; + continue; + } + } + } + + return -1; +} + +assert( breakAndReturn() == 5 ); + + diff --git a/src/bin/mu/mu-interp/test/rng.mu b/src/bin/mu/mu-interp/test/rng.mu new file mode 100644 index 000000000..8d8166db8 --- /dev/null +++ b/src/bin/mu/mu-interp/test/rng.mu @@ -0,0 +1,25 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +IM := 139968; +IA := 3877; +IC := 29573; +global int LAST = 42; + +\: gen_random (float; float x) +{ + LAST = (LAST * IA + IC) % IM; + (x * float(LAST)) / float(IM); +} + +\: doit (float; int N) +{ + if (N < 1) N = 1; + repeat (N-1) gen_random(100.0); + gen_random(100.0); +} + +doit(1000000); diff --git a/src/bin/mu/mu-interp/test/rng.py b/src/bin/mu/mu-interp/test/rng.py new file mode 100644 index 000000000..8e84adf6e --- /dev/null +++ b/src/bin/mu/mu-interp/test/rng.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +import sys + +IM = 139968 +IA = 3877 +IC = 29573 + +LAST = 42 + + +def gen_random(max): + global LAST + LAST = (LAST * IA + IC) % IM + return (max * LAST) / IM + + +def main(N): + + if N < 1: + N = 1 + gr = gen_random + for i in range(1, N): + gr(100.0) + print("%.9f" % gr(100.0)) + + +main(1000000) diff --git a/src/bin/mu/mu-interp/test/root_scope.mu b/src/bin/mu/mu-interp/test/root_scope.mu new file mode 100644 index 000000000..bac0f1db8 --- /dev/null +++ b/src/bin/mu/mu-interp/test/root_scope.mu @@ -0,0 +1,9 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +__root.int x = 2; +__root.int y = 1; +__root.assert(x + y == 3); + diff --git a/src/bin/mu/mu-interp/test/runtime.mu b/src/bin/mu/mu-interp/test/runtime.mu new file mode 100644 index 000000000..16f2d2a3b --- /dev/null +++ b/src/bin/mu/mu-interp/test/runtime.mu @@ -0,0 +1,11 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +let pname = runtime.lookup_name("+"); +assert(pname == "+"); + +(int;int,int) plus = runtime.lookup_function(pname); +assert(plus(1,2) == 3); diff --git a/src/bin/mu/mu-interp/test/short.mu b/src/bin/mu/mu-interp/test/short.mu new file mode 100644 index 000000000..12456c701 --- /dev/null +++ b/src/bin/mu/mu-interp/test/short.mu @@ -0,0 +1,8 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +short foo = 10; +assert(foo + short(1) == short(11)); diff --git a/src/bin/mu/mu-interp/test/sieve.mu b/src/bin/mu/mu-interp/test/sieve.mu new file mode 100644 index 000000000..c3eb0aa17 --- /dev/null +++ b/src/bin/mu/mu-interp/test/sieve.mu @@ -0,0 +1,38 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// Sieve imperative style +// + +\: nsieve (void; int m) +{ + int count = 0; + bool[] isPrime; + isPrime.resize(m); + + for (int i=2; i < m; i++) + isPrime[i] = true; + + for (int i = 2; i < m; i++) + { + if (isPrime[i]) + { + count++; + for (int j = i << 1; j < m; j += i) + isPrime[j] = false; + } + } + + print("Primes up to %8d %8d\n" % (m, count)); +} + +int m = 9; + +for (int i = 0; i < 3; i++) +{ + nsieve(10000 << (m-i)); +} + diff --git a/src/bin/mu/mu-interp/test/simple_class_decl.mu b/src/bin/mu/mu-interp/test/simple_class_decl.mu new file mode 100644 index 000000000..f205f98a3 --- /dev/null +++ b/src/bin/mu/mu-interp/test/simple_class_decl.mu @@ -0,0 +1,67 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +// Cliche OO class example +// + +class: Shape +{ + method: name (string;); + method: isPiecewiseLinear (bool;); + method: outputName (void;); +} + +class: Rectangle : Shape +{ + method: name (string;) { "Rectangle"; } + method: isPiecewiseLinear (bool;) { return true; } + method: outputName (void;) { print("Rectangle\n"); } +} + +class: Square : Rectangle +{ + method: name (string;) { "Square"; } + method: outputName (void;) { print("Square\n"); } +} + +class: Ellipse : Shape +{ + method: name (string;) { "Ellipse"; } + method: isPiecewiseLinear (bool;) { return false; } + method: outputName (void;) { print("Ellipse\n"); } +} + +class: Circle : Ellipse +{ + method: name (string;) { "Circle"; } + method: outputName (void;) { print("Circle\n"); } +} + +Shape circle = Circle(); +Shape ellipse = Ellipse(); +Shape square = Square(); +Shape rectangle = Rectangle(); +Shape shape = Shape(); + + +assert(circle.name() == "Circle"); +assert(ellipse.name() == "Ellipse"); +assert(square.name() == "Square"); +assert(rectangle.name() == "Rectangle"); +assert(rectangle.isPiecewiseLinear() == true); +assert(circle.isPiecewiseLinear() == false); +assert(square.isPiecewiseLinear() == true); +assert(ellipse.isPiecewiseLinear() == false); + + +// +// Make sure it throws on the unimplemented method +// + +bool worked = false; +try { shape.outputName(); } catch (...) { worked = true; } +assert(worked); + diff --git a/src/bin/mu/mu-interp/test/simple_float.mu b/src/bin/mu/mu-interp/test/simple_float.mu new file mode 100644 index 000000000..888f9e619 --- /dev/null +++ b/src/bin/mu/mu-interp/test/simple_float.mu @@ -0,0 +1,6 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +1.0 + 1.0 diff --git a/src/bin/mu/mu-interp/test/simple_interface.mu b/src/bin/mu/mu-interp/test/simple_interface.mu new file mode 100644 index 000000000..ba3e88d58 --- /dev/null +++ b/src/bin/mu/mu-interp/test/simple_interface.mu @@ -0,0 +1,25 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +// +// Sized is a interface to any type that has a "size" function of +// type (int;Thing) where Thing is the actual type. In addition, +// there is another function "bar" which may optionally be part of +// the type. Its optional because the interface implements a default +// version of the function -- so in this case, int[] can be cast to a +// Sized. +// + +// interface: Sized +// { +// \: bar (int; Sized x) { 123; } +// \: size (int; Sized); +// } + +// Sized foo = int[] {1, 2, 3}; +// assert(foo.size() == 3); +// assert(foo.bar() == 123); +// foo.size(); diff --git a/src/bin/mu/mu-interp/test/source_module.mu b/src/bin/mu/mu-interp/test/source_module.mu new file mode 100644 index 000000000..62edad7d8 --- /dev/null +++ b/src/bin/mu/mu-interp/test/source_module.mu @@ -0,0 +1,8 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +use imath; +assert(multiply(subtract(add(1,)(2),)(10), -2) == 14); +assert(answer == 42); diff --git a/src/bin/mu/mu-interp/test/spectral_norm.c b/src/bin/mu/mu-interp/test/spectral_norm.c new file mode 100644 index 000000000..c2137107c --- /dev/null +++ b/src/bin/mu/mu-interp/test/spectral_norm.c @@ -0,0 +1,58 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +/* -*- mode: c -*- + * + * The Great Computer Language Shootout + * http://shootout.alioth.debian.org/ + * + * Contributed by Sebastien Loisel + */ + +#include +#include +#include + +double eval_A(int i, int j) { return 1.0/((i+j)*(i+j+1)/2+i+1); } + +void eval_A_times_u(int N, const double u[], double Au[]) +{ + int i,j; + for(i=0;i + * Tod Olson + * + * This is the fp version of tak + */ + +function: tak (float; float x, float y, float z) +{ + if y >= x then z else tak(tak(x - 1.0, y, z), + tak(y - 1.0, z, x), + tak(z - 1.0, x, y)); +} + +float n = 7.0; +assert(tak(n * 3.0, n * 2.0, n * 1.0) == 14.0); +//assert(tak(28, 12, 6) == 7); diff --git a/src/bin/mu/mu-interp/test/tak.py b/src/bin/mu/mu-interp/test/tak.py new file mode 100644 index 000000000..b70f07b4d --- /dev/null +++ b/src/bin/mu/mu-interp/test/tak.py @@ -0,0 +1,20 @@ +#! /depot/python/arch/bin/python +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# takeuchi benchmark in Python +# see +# Keith Waclena + + +def tak(x, y, z): + if not (y < x): + return z + else: + return tak(tak(x - 1, y, z), tak(y - 1, z, x), tak(z - 1, x, y)) + + +# print tak(18, 12, 6) +print(tak(28, 12, 6)) diff --git a/src/bin/mu/mu-interp/test/trace.mu b/src/bin/mu/mu-interp/test/trace.mu new file mode 100644 index 000000000..61284bd78 --- /dev/null +++ b/src/bin/mu/mu-interp/test/trace.mu @@ -0,0 +1,37 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +require runtime; + +function: foo ([string]; int depth) +{ + if (depth == 4) + { + return runtime.backtrace(); + } + else + { + return foo(depth+1); + } +} + +[string] stuff = foo(0); + +if (stuff eq nil) +{ + print("NO BACKTRACE\n"); +} +else +{ + int i = 0; + + for_each (frame; stuff) + { + i++; + print("%s\n" % frame); + } +} + + diff --git a/src/bin/mu/mu-interp/test/tuple_type.mu b/src/bin/mu/mu-interp/test/tuple_type.mu new file mode 100644 index 000000000..4e09864e8 --- /dev/null +++ b/src/bin/mu/mu-interp/test/tuple_type.mu @@ -0,0 +1,39 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +T := (int,string,float); // tuple type created here + +T foo = T.__allocate(); // literal allocator call +foo._0 = 1; // the fields of the tuple +foo._1 = "hello"; // are numbered +foo._2 = math.pi; + +T bar = {11, "eleven", 11.11}; // default constructor +let baz = (12, "twelve", 12.12); // tuple expression + +let bigmess = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + "one", "two", "three", "six", "ten", + (vector float[3])(1, 2, 3), + int[] {10, 8, -10}, + float[] {math.pi, math.e}, + T(1, "one", 1), + float[4,4] {1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1}, + \: (int n) { \: (int x) { x + n; }; }, + 55); + +assert(bigmess._0 == 1 && bigmess._21 == 55); + +\: returns_tuple (float f, string s, int i) { (i, s, f); } + +let as = (1, 2, 3), + bf = 10, + bm = returns_tuple(123.321, "one two three", 321); + +assert(bf == 10); + diff --git a/src/bin/mu/mu-interp/test/unicode.mu b/src/bin/mu/mu-interp/test/unicode.mu new file mode 100644 index 000000000..d9b1f2f48 --- /dev/null +++ b/src/bin/mu/mu-interp/test/unicode.mu @@ -0,0 +1,20 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +let s = "这是一个测试的Unicode字符编码", + a = "هذا اختبار لليونيكود ترميز الأحر"; + +print("s = \"%s\", length = %d\n" % (s, s.size())); +print("c = %c\n" % '是'); + +assert(s.substr(7, 7) == "Unicode"); +assert(s[7] == "U"); +assert(s[14] == '字'); +assert(s.size() == 18); +string x = 'む'; +assert(x == 'む'); + +print("無 => %c\n" % 'む'); diff --git a/src/bin/mu/mu-interp/test/use.mu b/src/bin/mu/mu-interp/test/use.mu new file mode 100644 index 000000000..06237e02c --- /dev/null +++ b/src/bin/mu/mu-interp/test/use.mu @@ -0,0 +1,26 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +function: foo(int;) +{ + use math; + max(1,2); +} + +float f = 1.0; +math.abs(f); +use math; +abs(f); + +function: bar(int;) +{ + max(1,2); +} + +{ + use string; // using a type instead of module + assert(split("foo bar", " ").back() == "bar"); +} diff --git a/src/bin/mu/mu-interp/test/utf8.mu b/src/bin/mu/mu-interp/test/utf8.mu new file mode 100644 index 000000000..091420bb3 --- /dev/null +++ b/src/bin/mu/mu-interp/test/utf8.mu @@ -0,0 +1,12 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +let s = "む ⊗ ⅝ ⇒ ❶"; +string s2 = '\u3080'; +print("%s\n" % s); +print("%s\n" % s2); +print("%c\n" % '❶'); + diff --git a/src/bin/mu/mu-interp/test/var.mu b/src/bin/mu/mu-interp/test/var.mu new file mode 100644 index 000000000..04f363493 --- /dev/null +++ b/src/bin/mu/mu-interp/test/var.mu @@ -0,0 +1,7 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +float v=1.0,foo=10; +float i=1.23,bar=123.23123; diff --git a/src/bin/mu/mu-interp/test/variant.mu b/src/bin/mu/mu-interp/test/variant.mu new file mode 100644 index 000000000..335ae9382 --- /dev/null +++ b/src/bin/mu/mu-interp/test/variant.mu @@ -0,0 +1,199 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +Point := vector float[3]; + +union: Weekday { Monday | Tuesday | Wednesday | Thursday | Friday } + +union: NumberList { ListOfFloat [float] | + ListOfInt [int] | + ListOfShort [short] } + +union: Tree +{ + class: MidPoint { float value; int dimension; } + class: NodeValue { MidPoint p; Tree left; Tree right; } + + Leaf Point | Node NodeValue +} + +union: IntList +{ + Empty | Cons (int, IntList) +} + + +union: Foo +{ + class: Bar { Foo a; Bar b; int x; float y; vector float[3] p; } + + A int + | B float + | C vector float[3] + | D vector float[2] + | E vector float[4] + | F (void;Foo,Foo) + | G (int,float,int) + | H Bar + | I (Bar, Foo) + | J int[] +} + +union: StringIntMap +{ + Empty | MapEntry(string, StringIntMap, StringIntMap) +} + +//---------------------------------------------------------------------- + +\: testTree (void;) +{ + use Tree; + + Tree tree = Node(MidPoint(1.0, 3), Leaf(), Leaf(1,2,3)); + Tree leaf = Leaf(1,2,3); + + let n = Node(MidPoint(1.0, 2), Leaf(1,2,3), Leaf(4,5,6)), + Node {{a, b}, c, d} = n, + Leaf q = d; + + \: doit (void; Tree tree, Tree leaf) + { + case (leaf) + { + Leaf p -> { assert(p == Point(1,2,3)); } + Node {{v,d}, left, right} -> { throw "wrong!\n"; } + } + + case (tree) + { + Leaf p -> { throw "wrong"; } + + Node {{v,d}, left, right} -> + { + //print("Node ((%f,%d), %s, %s)\n" % (v,d,left,right)); + assert(v == 1.0); + assert(d == 3); + } + } + } + + doit(tree, leaf); +} + +\: testEnum (void;) +{ + // + // Constructors are in scope of union type. Otherwise "use" + // the union to get its constructors in scope + // + + \: abbreviation (string; Weekday day) + { + case (day) + { + // + // Inside case statement the expression type is in scope + // so no prefix (Weekday.Monday) necessary + // + + Friday -> { return "fri"; } + Monday -> { return "mon"; } + Thursday -> { return "thurs"; } + Tuesday -> { return "tues"; } + Wednesday -> { return "wed"; } + } + + return "bad"; + } + + assert(abbreviation(Weekday.Friday) == "fri"); + assert(abbreviation(Weekday.Thursday) == "thurs"); + assert(abbreviation(Weekday.Wednesday) == "wed"); + assert(abbreviation(Weekday.Tuesday) == "tues"); + assert(abbreviation(Weekday.Monday) == "mon"); +} + +\: testList (void;) +{ + use IntList; + + IntList list = Empty; + + print("building..."); + for (int i=99999; i >= 0; i--) list = Cons(i, list); + print("done\n"); + + \: length (int; IntList l) + { + bool done = false; + int count = 0; + + while (true) + { + case (l) + { + Empty -> { return count; } + Cons (_,y) -> { l = y; count++; } + } + } + + return 0; + } + + print("counting..."); + print("length of list is %d\n" % length(list)); +} + + +\: testFoo (void;) +{ + use Foo; + + let q = I(Bar(A(1), Bar(), 1, math.pi, (vector float[3])(1,2,3)), A(99)); + + case (q) + { + C x -> { print("%g, %g, %g\n" % (x.x, x.y, x.z)); } + + I ({a, b, x, y, p}, foo) -> + { + print("a = %s, b = %s, x = %d, y = %g, z = (%g, %g, %g) *and* foo = %s\n" + % (a, b, x, y, p.x, p.y, p.z, foo)); + } + } +} + +\: variantWithList (void;) +{ + use NumberList; + + let ilist = ListOfInt([1,2,3,4]), + flist = ListOfFloat([1.0, 2.0, 4.0]), + slist = ListOfShort([short(123)]); + + let ListOfInt one:two:other = ilist, + ListOfFloat fone:ftwo:fother = flist, + ListOfShort l = slist; + + assert(one == 1); + assert(two == 2); + assert(head(other) == 3); + assert(head(tail(other)) == 4); + assert(fone == 1.0); + assert(ftwo == 2.0); + assert(head(fother) == 4.0); + assert(head(l) == 123); +} + +testEnum(); +testTree(); +testList(); +testFoo(); +variantWithList(); + +//require autodoc; +//print(autodoc.document_symbol("IntList")); diff --git a/src/bin/mu/mu-interp/test/vec.mu b/src/bin/mu/mu-interp/test/vec.mu new file mode 100644 index 000000000..57bfca2a4 --- /dev/null +++ b/src/bin/mu/mu-interp/test/vec.mu @@ -0,0 +1,23 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// + +v4 := vector float[4]; +v3 := vector float[3]; + +float a = 1; +float b = 2; +v4 v0 = (v4(1,2,3,4) + v4(1,1,1,1)) / v4(2,2,2,2); +v4 v1 = v4(a,b,a,b); +v4 v2 = (v0 * v1) + v0 / v1; + +operator: % (v3; v3 a, v3 b) { cross(a,b); } +operator: ^ (float; v3 a, v3 b) { dot(a,b); } + +v3 av = v3(1, 0, 0); +v3 bv = v3(0, 1, 0); +v3 cv = cross(av, bv); +assert(mag(cv) == 1.0); +assert((v3(0, 1, 0) ^ v3(1, .5, 0)) == .5); diff --git a/src/bin/mu/mu-interp/testdata/crowd_L13.parameters b/src/bin/mu/mu-interp/testdata/crowd_L13.parameters new file mode 100644 index 000000000..9416656c7 --- /dev/null +++ b/src/bin/mu/mu-interp/testdata/crowd_L13.parameters @@ -0,0 +1,243 @@ +# -*- mode: shell-script -*- +# +# Parameter file for sprite generation +# +# SYNTAX +# ------ +# +# * Each line is a parameter assignment +# or a comment or blank +# +# * A '#' on a line starts a comment and goes +# until the end of the line +# +# * lines are of the form: PARAM ARG = VALUE(S) +# the ARG part is only needed for some +# parameters +# +# * some parameters have an extra argument +# like "angle" or "actor" +# +# * some parameters take three values ("target") +# +# * some parameters are boolean (yes, true, 1) + +# Some basic parameters +# +# Note: Boolean paramters can be "yes", "true", or "1" for true, anything +# else for false. +# +# New selection method !! +# selection method: random or linear, +# defaults to random (the old way), so far linear yields fewer twins +# selection method: use linear +# +######################## +# +# a note about the seeds: First change the "emitSeed" number +# (by one at a time is OK) to get a new random position +# and sprite choice. If you like the physical distribution, +# stop changing "emitSeed". If you still need to try different +# random sprite distributions without changing how they are layed +# out, change just the "seed". emitSeed will change the random seed +# used to place the particles and which sprite gets put at each +# particle position, seed will change which sprite gets put at +# each particle +# +######################## + + +# stuff that gets changed most often: +startFrame = 1 +endFrame = 65 +offset = -10 20 # start frame offset of -10 to 10 +staticOffset = 90 110 # start frame offset of -10 to 10 + +# for upper stands +#emitSeed = 123 +#seed = 123 +#skipProbability = 0.35 # use 0.35 for upper stands +#file in_sprite_a000fsa7 = 0.02 # banners +#file in_sprite_a000fsa8 = 0.03 # banners +#file in_sprite_a000fsa9 = 0.10 # banners + +#for lower stands +emitSeed = 123 +seed = 123 +skipProbability = 0.25 # use 0.25 for lower stands +file in_sprite_a000fsa7 = 0.03 # banners +file in_sprite_a000fsa8 = 0.04 # banners +file in_sprite_a000fsa9 = 0.13 # banners + + +# #all banners +# file in_sprite_a000fsa71dA01 = 0.03 # banner - go anyone +# file in_sprite_a000fsa72dA01 = 0.03 # banner - hi mom +# file in_sprite_a000fsa74dA01 = 0.03 # banner - will win +# file in_sprite_a000fsa75dA01 = 0.03 # banner - stan the man +# file in_sprite_a000fsa76dA01 = 0.03 # banner - just win +# file in_sprite_a000fsa77dA01 = 0.03 # banner - this is our year 76 +# file in_sprite_a000fsa78dA01 = 0.03 # banner - on the road to victory +# +# file in_sprite_a000fsa81dA01 = 0.05 # banner - go anyone +# file in_sprite_a000fsa82dA01 = 0.05 # banner - hi mom +# file in_sprite_a000fsa84dA01 = 0.05 # banner - will win +# file in_sprite_a000fsa85dA01 = 0.05 # banner - stan the man +# file in_sprite_a000fsa86dA01 = 0.05 # banner - just win +# file in_sprite_a000fsa87dA01 = 0.05 # banner - this is our year 76 +# file in_sprite_a000fsa88dA01 = 0.05 # banner - on the road to victory +# +# file in_sprite_a000fsa91dA01 = 0.14 # banner - go anyone +# file in_sprite_a000fsa92dA01 = 0.14 # banner - hi mom +# file in_sprite_a000fsa94dA01 = 0.14 # banner - will win +# file in_sprite_a000fsa95dA01 = 0.14 # banner - stan the man +# file in_sprite_a000fsa96dA01 = 0.14 # banner - just win +# file in_sprite_a000fsa97dA01 = 0.14 # banner - this is our year 76 +# file in_sprite_a000fsa98dA01 = 0.14 # banner - on the road to victory + + +# use a sequence version of the stands +#geomfile = /net/show/invincible/assets/stadium/gto/stands_v004.gto + +# use a shot version of the stands +geomfile = /net/show/invincible/master/inc/inc022//gto/inc022_stands_v002.gto + +geomobject = stands_LShape13 +suffix = A # card + suffix, cardA in this case +cardSize = 130 130 # width height WATCH YOUR UNITS! +cardOffset = .130 .43 0 # Offset of cards X & Y = % of cardSize, Z = worldSpace +target = 0 0 0 # used by MAX +flip = yes # Randomly flip sprites +alwaysFlip = no # Always flip sprites (overrides 'flip' if true) +outfile = out.ms # Ignored for Tweak +cacheOutput = gto/cache/crowd_L13 +select = linear + +# c4 params + +seperation = 49.75 # UNIT of length (foot, meter, inch, etc) +stagger = 5 +margin = 0.0 + +densityMap = None #gto/Test_stand_A_dist.tif +# lightingMap = light.tif + +# parameters which apply weights + +angle 0 = 1.0 +angle 30 = 1.0 +angle 60 = 0.0 +angle 80 = 0.0 +angle 120 = 0.0 +angle 160 = 0.0 +angle 180 = 0.0 + +forwardFacing = 1.0 +cameraFacing = 1.0 +summerClothes = 1.0 +winterClothes = 1.0 +aClothes = 1.0 +bClothes = 1.0 +cClothes = 1.0 +dClothes = 1.0 +lit = 1.0 # lit is a weight, alternately "ambient = ..." +ambient = 1.0 # ambient is a weight, alternately "lit = ..." +teamEagles = 1.0 +teamCowboys = 1.0 +teamNeutral = 1.0 + +action sit1 = 1 +action boo = 0 +action clap = 1 +action clap2 = 0.25 +action sit2 = 0 +action stand = 0 +action standClap = 0.75 +action sit3 = 0 +action sit4 = 0 + +# for each category, the "action" parameter above will be made +# static in the proportion of the static multiplier below +# for instance if "action clap" is set to 1 and "static clap" +# is set to 0.8, 80% of the clappers will be static + +static sit1 = 0.5 +static boo = 0.5 +static clap = 0.5 +static clap2 = 0.5 +static sit2 = 0.5 +static stand = 0.5 +static standClap = 0.75 +static sit3 = 0.5 +static sit4 = 0.5 + +# Full sprite file filter in case you need it +# e.g., isolate one file and not use it +# or use more of one you like + +## # dial down bad actors and teal shirted sprites +actor 02 = 0.01 # all bobs set to 0.01 +## actor 04 = 0.33 # all excited lady set to 0.33 + +## weight the sprites angles here +file in_sprite_a000fs = 1 +file in_sprite_k000fs = 1 +file in_sprite_a030cs = 0.75 +file in_sprite_k030cs = 0.75 +file in_sprite_a030cw = 0.75 +file in_sprite_a030fs = 0.75 +#we don't have any of these +#file in_sprite_k030fs = 0 + +# sprite multipliers to adjust the color +file in_sprite_a030cwa16nA01 = 0.6 # red sweatshirt guy +file in_sprite_a030cwa21eA02 = 0.5 # green winter coat guy with beard +file in_sprite_a030cwa11eA01 = 0.5 # green winter coat guy 2 +file in_sprite_a030csa43nA09 = 0.5 # light green guy +file in_sprite_k030csa37nA10 = 0.5 # light green guy +file in_sprite_k030csd23nA06 = 0.5 # light green guy +file in_sprite_k000fsd17nB06 = 0.3 # light green guy +file in_sprite_k000fsd08nB06 = 0.3 # light green shirt guy +file in_sprite_a000fsc01nB05 = 0.3 # college boy in red +file in_sprite_a000fsc04nB04 = 0.2 # green shirt woman +file in_sprite_a000fsa05nB02 = 0.2 # pink shirt girl +file in_sprite_k000fsd22nB06 = 0.2 # salmon shirt girl +file in_sprite_k030csd22nA06 = 0.2 # salmon shirt girl +file in_sprite_a030cwa21eA02 = 0.2 # green winter guy +file in_sprite_a000fsc21eB05 = 0.2 # drk green shirt, bearded man with hat +file in_sprite_a000fsa42nB09 = 0.1 # red shirt guy +file in_sprite_a030csc23eA05 = 0.02 # green arm waving guy + +file in_sprite_a000fsc01nQ05 = 1.35 # new blue shirt, college boy +file in_sprite_a000fsa29eQ10 = 1.35 # new blue shirt, bald guy +file in_sprite_a000fsc05nQ04 = 1.35 # new blue shirt, woman with long brown hair +file in_sprite_a000fsa21eQ03 = 1.35 # new blue shirt person +file in_sprite_a000fsa42nQ09 = 1.35 # new blue shirt person +file in_sprite_a000fsb48nQ11 = 1.35 # new blue shirt person +file in_sprite_a000fsc04nQ04 = 1.35 # new blue shirt person +file in_sprite_a000fsc10nQ05 = 1.35 # new blue shirt person +file in_sprite_a000fsc21eQ05 = 1.35 # new blue shirt person - bearded guy +file in_sprite_k030cse61nA07 = 1.35 # todd +file in_sprite_a030csa23nA03 = 1.35 # guy with hat and yellow shirt +file in_sprite_a000fsc13cB05 = 1.35 # woman with cowboy hat and yellow shirt +file in_sprite_a030fsc15nA05 = 1.35 # man with orange vest +file in_sprite_k000fsa32nB10 = 1.35 # kid with yellow tee shirt +file in_sprite_a030csb50nA11 = 1.35 # scott with orange shirt + + + +# sprites that we never want to see +file in_sprite_a000fsc01nT05 = 0 # teal blue shirt, college boy with hat +file in_sprite_a000fsa04nT02 = 0 # teal blue shirt, too saturated, excited woman +file in_sprite_a000fsa04nG02 = 0 # teal blue shirt, excited woman +file in_sprite_a000fsc04nT04 = 0 # teal blue printed shirt, excited woman +file in_sprite_a000fsa05nT02 = 0 # teal blue shirt, long black hair +file in_sprite_a000fsc05nT04 = 0 # teal blue shirt, long black hair number2 +file in_sprite_a000fsa21eT03 = 0 # teal blue knit hat, bearded man +file in_sprite_a000fsc21eT05 = 0 # teal blue shirt, bearded man with baseball hat +file in_sprite_a000fsa29eT10 = 0 # too saturated ,bald guy +file in_sprite_a000fsa29eX10 = 0 # dark teal blue shirt, bald guy +file in_sprite_a000fsa29eY10 = 0 # light teal blue shirt, bald guy +file in_sprite_a000fsa42nT09 = 0 # teal blue shirt, fat guy with open shirt +file in_sprite_a000fsa05nQ02 = 0 # new blue shirt, woman with long brown hair, doesn't look as good as "Q04" + diff --git a/src/bin/mu/mu-interp/valgrind.supp b/src/bin/mu/mu-interp/valgrind.supp new file mode 100644 index 000000000..a7544d0b9 --- /dev/null +++ b/src/bin/mu/mu-interp/valgrind.supp @@ -0,0 +1,132 @@ +{ + MoreGarbage1 + Memcheck:Value4 + fun:_ZNK2Mu6Object4typeEv + fun:_ZN2Mu16GarbageCollector4markEPKNS_7ProcessE +} + +{ + ClassInstanceGrabage1 + Memcheck:Value4 + fun:_ZN2Mu13ClassInstance9structureEv + fun:_ZNK2Mu5Class13markRecursiveEPNS_6ObjectEb +} + +{ + ClassGarbage1 + Memcheck:Cond + fun:_ZNK2Mu5Class13markRecursiveEPNS_6ObjectEb + fun:_ZN2Mu6Object13markRecursiveEb +} + +{ + MuGarbageAllocationTest7 + Memcheck:Value4 + fun:_ZNK7stl_ext16markable_pointer4markEv + fun:_ZN2Mu6Object13markRecursive* +} + +{ + MuGarbageAllocationTest6 + Memcheck:Value4 + fun:_ZNK2Mu6Object4typeEv + fun:_ZN2Mu6Object13markRecursiveEb +} + +{ + MuGarbageAllocationTest5 + Memcheck:Value4 + fun:_ZN7stl_ext16markable* + fun:_ZN2Mu6Object13markRecursiveEb +} + +{ + MuGarbageAllocationTest3 + Memcheck:Value4 + fun:_ZNK2Mu6Object4typeEv + fun:_ZN2Mu16GarbageCollector4markEPKNS_7ProcessE + fun:_ZN2Mu16GarbageCollector7collectEv + fun:_ZN2Mu14arenaCollectorEPN7stl_ext17block_alloc_arenaE +} + +{ + MuGarbageAllocationTest2 + Memcheck:Cond + fun:_ZNK7stl_ext17fixed_block_arena12is_allocatedEPKv + fun:_ZNK7stl_ext17block_alloc_arena12is_allocatedEPKv + fun:_ZN2Mu16GarbageCollector4markEPKNS_7ProcessE + fun:_ZN2Mu16GarbageCollector7collectEv +} + +{ + MuGarbageAllocationTest + Memcheck:Cond + fun:_ZNK7stl_ext21fixed_block_allocator12is_allocatedEPKv + fun:_ZNK7stl_ext17fixed_block_arena12is_allocatedEPKv + fun:_ZNK7stl_ext17block_alloc_arena12is_allocatedEPKv + fun:_ZN2Mu16GarbageCollector4markEPKNS_7ProcessE +} + +{ + GarbageCollectorStackCrawl + Memcheck:Cond + fun:_ZN2Mu16GarbageCollector4markEPKNS_7ProcessE + fun:_ZN2Mu16GarbageCollector7collectEv + fun:_ZN2Mu14arenaCollectorEPN7stl_ext17block_alloc_arenaE + fun:_ZN7stl_ext17block_alloc_arena8allocateEj +} + +{ + ExceptionBacktrace + Memcheck:Cond + fun:_ZNK2Mu6Thread9backtraceERSt6vectorIPNS_4NodeESaIS3_EE + fun:_ZN2Mu9ExceptionC2ERNS_6ThreadEPKcPNS_6ObjectE + fun:* +} + +{ + ExceptionBacktrace2_Questionable + Memcheck:Cond + fun:_ZNK7stl_ext21fixed_block_allocator12is_allocatedEPKv + fun:_ZNK7stl_ext17fixed_block_arena12is_allocatedEPKv + fun:_ZNK7stl_ext17block_alloc_arena12is_allocatedEPKv + fun:_ZNK2Mu6Thread9backtraceERSt6vectorIPNS_4NodeESaIS3_EE +} + + +{ + GL + Memcheck:Param + ioctl(generic) + fun:ioctl + obj:/usr/lib/libGL.so.1.0.8174 +} + +{ + GLCond + Memcheck:Cond + obj:/usr/lib/libGLcore.so.1.0.8174 +} + +{ + GLCond2 + Memcheck:Cond + fun:_nv000026gl + obj:/usr/lib/libGLcore.so.1.0.8174 + obj:* +} + +{ + NVGLCond + Memcheck:Cond + fun:_nv000025gl + obj:/usr/lib/libGLcore.so.1.0.8174 +} + +{ + GLstrcat + Memcheck:Cond + fun:strcat + obj:/usr/lib/libGLcore.so.1.0.8174 +} + diff --git a/src/bin/nsapps/CMakeLists.txt b/src/bin/nsapps/CMakeLists.txt new file mode 100644 index 000000000..c13e177d8 --- /dev/null +++ b/src/bin/nsapps/CMakeLists.txt @@ -0,0 +1,7 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +ADD_SUBDIRECTORY(RV) diff --git a/src/bin/nsapps/RV/CMakeLists.txt b/src/bin/nsapps/RV/CMakeLists.txt new file mode 100644 index 000000000..a2dee309e --- /dev/null +++ b/src/bin/nsapps/RV/CMakeLists.txt @@ -0,0 +1,82 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) +INCLUDE(CPack) + +SET(_target + "RV" +) + +FIND_PACKAGE( + Qt5 + COMPONENTS Gui + REQUIRED +) + +SET(_sources + main.cpp +) + +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +TARGET_COMPILE_OPTIONS( + ${_target} + PRIVATE -DGIT_HEAD=\"${RV_GIT_COMMIT_SHORT_HASH}\" -DUSE_SIDECARS=1 -DMAJOR_VERSION=${RV_MAJOR_VERSION} -DMINOR_VERSION=${RV_MINOR_VERSION} + -DREVISION_NUMBER=${RV_REVISION_NUMBER} -DRELEASE_DESCRIPTION=\"${RV_RELEASE_DESCRIPTION}\" +) + +# +# Platform common libraries +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE DarwinBundle + IOproxy + MovieProxy + MovieSideCar + OpenEXR::OpenEXR + MovieFB + MovieProcedural + Mu + MuGL + MuIO + MuTwkApp + PyTwkApp + Qt5::Gui + IPCore + RvApp + RvCommon + RvPackage + TwkQtCoreUtil + TwkApp + TwkDeploy + TwkExc + TwkFB + TwkGLF + TwkMovie + TwkUtil + arg + stl_ext + BDWGC::Gc +) + +RV_STAGE(TYPE "MAIN_EXECUTABLE" TARGET ${_target}) + +FILE(GLOB _icns_files *.icns) +FOREACH( + _icns_file + ${_icns_files} +) + CONFIGURE_FILE(${_icns_file} ${RV_STAGE_RESOURCES_DIR} COPYONLY) +ENDFOREACH() diff --git a/src/bin/nsapps/RV/Info.plist b/src/bin/nsapps/RV/Info.plist new file mode 100644 index 000000000..6737bcee2 --- /dev/null +++ b/src/bin/nsapps/RV/Info.plist @@ -0,0 +1,612 @@ + + + + + CFBundleURLTypes + + + CFBundleURLName + rvlink URL + CFBundleURLSchemes + + rvlink + + + + CFBundleDevelopementRegion + English + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + exr + + CFBundleTypeName + OpenEXR Image + CFBundleTypeOSTypes + + EXR + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + tif + tiff + + CFBundleTypeName + TIFF Image + CFBundleTypeOSTypes + + TIFF + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + rla + + CFBundleTypeName + Wavefront/3DStudio RLA Image + CFBundleTypeOSTypes + + RLA + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + rgbe + + CFBundleTypeName + Radiance RGBE Image + CFBundleTypeOSTypes + + RGBE + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + tga + + CFBundleTypeName + Targa Image + CFBundleTypeOSTypes + + TGA + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + iff + + CFBundleTypeName + IFF Image + CFBundleTypeOSTypes + + IFF + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + jpg + jpeg + + CFBundleTypeName + JPEG Image + CFBundleTypeOSTypes + + JPEG + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + jp2 + j2k + jpc + + CFBundleTypeName + JPEG-2000 Image + CFBundleTypeOSTypes + + JPEG-2000 + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + cin + cineon + + CFBundleTypeName + Cineon Image + CFBundleTypeOSTypes + + CIN + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + dpx + + CFBundleTypeName + DPX Image + CFBundleTypeOSTypes + + DPX + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + gif + + CFBundleTypeName + GIF Image + CFBundleTypeOSTypes + + GIF + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + png + + CFBundleTypeName + PNG Image + CFBundleTypeOSTypes + + PNG + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + psd + + CFBundleTypeName + Cineon Image + CFBundleTypeOSTypes + + PSD + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + cr2 + crw + rmf + + CFBundleTypeName + Canon Raw Image + CFBundleTypeOSTypes + + Canon Raw + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + nef + + CFBundleTypeName + Nikon Raw Image + CFBundleTypeOSTypes + + Nikon Raw + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + xcf + + CFBundleTypeName + Gimp Image + CFBundleTypeOSTypes + + XCF + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + mov + + CFBundleTypeName + Quicktime Movie + CFBundleTypeOSTypes + + MOV + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + dv + + CFBundleTypeName + Quicktime Movie + CFBundleTypeOSTypes + + DV + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + mp4 + + CFBundleTypeName + MPEG4 Movie + CFBundleTypeOSTypes + + MP4 + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + avi + AVI + + CFBundleTypeName + AVI Movie + CFBundleTypeOSType + + AVI + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + r3d + R3D + + CFBundleTypeName + RED Raw Movie + CFBundleTypeOSType + + R3D + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + ari + ARI + + CFBundleTypeName + ARRI Raw Movie + CFBundleTypeOSType + + ARI + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + gtv + GTV + + CFBundleTypeName + GTV Movie + CFBundleTypeOSType + + GTV + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + rv + RV + + CFBundleTypeIconFile + rvfile.icns + CFBundleTypeName + RV Session + CFBundleTypeOSType + + RV + + CFBundleTypeRole + Editor + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + movieproc + + CFBundleTypeIconFile + rvfile.icns + CFBundleTypeName + RV Procedural Movie + CFBundleTypeOSType + + MovieProc + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + stdinfb + + CFBundleTypeIconFile + rvfile.icns + CFBundleTypeName + RV stdin Movie (from frames) + CFBundleTypeOSType + + StdinFB + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + rv3dlut + rvchlut + + CFBundleTypeName + RV LUT + CFBundleTypeOSType + + RV LUT LUT + + CFBundleTypeRole + None + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + .rvclips + + CFBundleTypeIconFile + rvdir.icns + CFBundleTypeName + RV Clip Bundle + CFBundleTypeOSType + + RVClipBundle + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + aiff + aif + + CFBundleTypeIconFile + rvdir.icns + CFBundleTypeName + AIFF Audio File + CFBundleTypeOSType + + AIFF + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + aifc + + CFBundleTypeIconFile + rvdir.icns + CFBundleTypeName + Apple AIFC Sound File + CFBundleTypeOSType + + AIFC + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + wav + + CFBundleTypeIconFile + rvdir.icns + CFBundleTypeName + Microsoft WAVE Sound File + CFBundleTypeOSType + + WAV + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + au + + CFBundleTypeIconFile + rvdir.icns + CFBundleTypeName + Sun Microsystems AU Sound File + CFBundleTypeOSType + + AU + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleTypeExtensions + + .snd + + CFBundleTypeIconFile + rvdir.icns + CFBundleTypeName + NeXT Sound File + CFBundleTypeOSType + + AU + + CFBundleTypeRole + Viewer + LSIsAppleDefaultForType + + + + CFBundleExecutable + RV + CFBundleGetInfoString + ${RV_MAJOR_VERSION}.${RV_MINOR_VERSION}.${RV_REVISION_NUMBER}, Copyright ${RV_VERSION_YEAR} Autodesk, Inc. + CFBundleIconFile + RV.icns + CFBundleIdentifier + com.autodesk.RV + CFBundleName + Open RV + CFBundlePackageType + APPL + CFBundleResourcesFileMapped + + CFBundleShortVersionString + ${RV_MAJOR_VERSION}.${RV_MINOR_VERSION}.${RV_REVISION_NUMBER} + CFBundleVersion + ${RV_MAJOR_VERSION}.${RV_MINOR_VERSION} + LSHasLocalizedDisplayName + 0 + LSMinimumSystemVersion + 10.8.0 + NSAppleScriptEnabled + 0 + NSHumanReadableCopyright + Copyright ${RV_VERSION_YEAR} Autodesk, Inc. + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/src/bin/nsapps/RV/RV.icns b/src/bin/nsapps/RV/RV.icns new file mode 100644 index 000000000..d6eed491e Binary files /dev/null and b/src/bin/nsapps/RV/RV.icns differ diff --git a/src/bin/nsapps/RV/main.cpp b/src/bin/nsapps/RV/main.cpp new file mode 100644 index 000000000..6b340fdb4 --- /dev/null +++ b/src/bin/nsapps/RV/main.cpp @@ -0,0 +1,733 @@ +//****************************************************************************** +// Copyright (c) 2008 Tweak Software. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern const char* rv_mac_dark; +extern const char* rv_mac_aqua; + +// +// Check that a version actually exists. If we're compiling opt error +// out in cpp. Otherwise, just note the lack of version info. +// + +#if MAJOR_VERSION == 99 +#if NDEBUG +//#error ********* NO VERSION INFORMATION *********** +#else +#warning ********* NO VERSION INFORMATION *********** +#endif +#endif + +using namespace std; +using namespace TwkFB; +using namespace TwkMovie; +using namespace TwkUtil; + +extern const char* TweakDark; + +static void control_c_handler(int sig) +{ + cout << "INFO: stopped by user" << endl; + exit(1); +} + +static int rtPeriod = 240; +static int rtComputation = 720; +static int rtConstraint = 360; +static int rtPreemptable = 1; + + +static int +set_realtime(int period, int computation, int constraint) +{ + struct thread_time_constraint_policy ttcpolicy; + + ttcpolicy.period=period; // HZ/160 + ttcpolicy.computation=computation; // HZ/3300; + ttcpolicy.constraint=constraint; // HZ/2200; + ttcpolicy.preemptible=1; + + if (thread_policy_set(mach_thread_self(), + THREAD_TIME_CONSTRAINT_POLICY, + (thread_policy_t)&ttcpolicy, + THREAD_TIME_CONSTRAINT_POLICY_COUNT) != KERN_SUCCESS) + { + return 0; + } + + return 1; +} + +void thread_priority_init() +{ + struct thread_time_constraint_policy ttcpolicy; + int ret, bus_speed, mib [2] = { CTL_HW, HW_BUS_FREQ }; + size_t len; + + len = sizeof(bus_speed); + + if (sysctl (mib, 2, &bus_speed, &len, NULL, 0) < 0) + { + cout << "ERROR: unable to get system bus frequency" << endl; + return; + } + + // + // The period is the number of cycles over which we are guaranteed + // a number of clock cycles equal to the second argument (the + // computation). The third argument (the constraint) is the + // maximum number of cycles to complete. + // + // from esound example: (160, 3300, 2200) + // cdaudio example: (120, 1440, 720) + // rv: (240, 720, 360) + // + // My interpretation of this: The denominators below are + // Hz. So 240 (rtPeriod) means 240 times a second. The *minimum* + // number of slices we'll get is: + // + // 240 * (720 / 360) => 580 slices/sec + // + // where 720 is the rtComputation and 360 is rtConstraint. + // + // These will *not* be evenly spaced, but no slice will last + // longer than 1/360 seconds probably much shorter. However, if + // you add up all the time spent in a second in our slices it + // will be at least 1/720 seconds. + // + // + + ttcpolicy.period = bus_speed / rtPeriod; + ttcpolicy.computation = bus_speed / rtComputation; + ttcpolicy.constraint = bus_speed / rtConstraint; + ttcpolicy.preemptible = rtPreemptable != 0; + + if (thread_policy_set(mach_thread_self(), + THREAD_TIME_CONSTRAINT_POLICY, + (int *)&ttcpolicy, + THREAD_TIME_CONSTRAINT_POLICY_COUNT) + != KERN_SUCCESS) + { + cout << "INFO: running without real-time scheduling" << endl; + } + else + { + cout << "INFO: real-time thread priorities set (bus=" + << (bus_speed/1000) << "kHz)" + << endl; + } +} + +string scarfFile(const string& fileName) +{ + ifstream in(fileName.c_str()); + stringstream buffer; + buffer << in.rdbuf(); + return buffer.str(); +} + +// +// We happen to know that Mu objects NEVER get stashed in 3rd party +// static areas so let's just remove all of them. +// + +static string qtver; + +static int gc_filter (const char* name, void* ptr, size_t size) +{ + string s(name); + + if (s.find("/Library/Frameworks/") != string::npos || + s.find("/System/Library/Frameworks/") != string::npos || + s.find("/usr/lib/") != string::npos || + s.find("imageformats/libq") != string::npos || + s.find(qtver) != string::npos) + { + //cout << "SKIPPING: " << name << endl; + return 0; + } + else + { + return 1; + } +} + +static void showDevices(TwkApp::VideoModule* m) +{ + for (auto d : m->devices()) cout << "INFO: " << m->name() << " found " << d->name() << endl; +} + +void createDevices(Rv::RvDocument* doc, Rv::RvApplication* app) +{ + // This is where video output devices are created (optional) +} + +int main(int argc, char *argv[]) +{ + if (!getenv("HOME")) + { + cerr << "ERROR: $HOME is not set in the environment and is required." << endl; + exit(-1); + } + + // + // As of OSX 10.7, interesting settings of the locale can cause crashes in apple core code. + // + if(getenv("LANG")) + { + std::string originalLocale = getenv("LANG"); + setenv("ORIGINALLOCAL", originalLocale.c_str(), 1); + } + else + { + setenv("ORIGINALLOCAL", "en", 1); + } + setenv ("LANG", "C", 1); + setenv ("LC_ALL", "C", 1); + + // Qt 5.12.1 specific + // Disable Qt Quick hardware rendering because QwebEngineView conflicts with QGLWidget + setenv( "QT_QUICK_BACKEND", "software", 0 /* changeFlag : Do not change the existing value */); + + // Qt 5.12.1 specific + // Prevent Mac from automatically scaling app pixel coordinates in OpenGL + setenv("QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE", "0", 0); /* changeFlag : Do not change the existing value */ + + // Prevent usage of native sibling widgets on Mac. This attribute can be removed + // if GLView is changed to inherit from QOpenGLWidget. + QApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); + + const char* pythonPath = getenv("PYTHONPATH"); + if (pythonPath) pythonPath = strdup(pythonPath); + + #ifndef PLATFORM_WINDOWS + // + // Check the per-process limit on open file descriptors and + // reset the soft limit to the hard limit. + // + struct rlimit rlim; + getrlimit (RLIMIT_NOFILE, &rlim); + rlim_t target = rlim.rlim_max; + if (OPEN_MAX < rlim.rlim_max) target = OPEN_MAX; + rlim.rlim_cur = target; + setrlimit (RLIMIT_NOFILE, &rlim); + getrlimit (RLIMIT_NOFILE, &rlim); + if (rlim.rlim_cur < target) + { + cerr << "WARNING: unable to increase open file limit above " << rlim.rlim_cur << endl; + } + #endif + + { + string qt(QT_VERSION_STR); + ostringstream ss; + ss << "Versions/" << qt[0] << "/Qt"; + qtver = ss.str(); + } + + TwkFB::ThreadPool::initialize(); + TwkUtil::MemPool::initialize(); + + string altPrefsPath; + for (size_t i = 0; i < argc; i++) + { + if (i < argc-1 && !strcmp(argv[i], "-prefsPath")) altPrefsPath = argv[i+1]; + } + Rv::RvApplication::initializeQSettings (altPrefsPath); + + TwkApp::DarwinBundle bundle("RV", + MAJOR_VERSION, + MINOR_VERSION, + REVISION_NUMBER, + true /*register rvlink protocol handler*/); + + bundle.setEnvVar("RV_PYTHONPATH_EXTERNAL", (pythonPath) ? pythonPath : ""); + + // + // Expand any rvlink urls in argv + // + + vector newArgv; + + for (int i = 0; i < argc; ++i) + { + if (0 == strncmp ("rvlink://", argv[i], 9)) + { + Rv::RvApplication::parseURL (argv[i], newArgv); + } + else newArgv.push_back (argv[i]); + } + argv = &(newArgv[0]); + argc = newArgv.size(); + + bool noPrefs = false; + bool resetPrefs = false; + + for (size_t i = 0; i < argc; i++) + { + if (!strcmp(argv[i], "-noPrefs")) noPrefs = true; + else if (!strcmp(argv[i], "-resetPrefs")) resetPrefs = true; + + else if (!strcmp(argv[i], "-bakeURL")) + { + string url = Rv::RvApplication::bakeCommandLineURL (argc, argv); + cerr << "Baked URL: " << url << endl; + exit(0); + } + else if (!strcmp(argv[i], "-encodeURL")) + { + string url = Rv::RvApplication::encodeCommandLineURL (argc, argv); + cerr << "Encoded URL: " << url << endl; + exit(0); + } + else if (!strcmp(argv[i], "-registerHandler")) + { + bundle.registerHandler(); + cerr << "INFO: registering '" << argv[0] << "' as default rvlink protocol handler." << endl; + exit(0); + } + } + + // + // Call libarg + // + + Rv::Options& opts = Rv::Options::sharedOptions(); + if (resetPrefs) Rv::RvPreferences::resetPreferencesFile(); + if (!noPrefs) Rv::RvPreferences::loadSettingsIntoOptions(opts); + else Rv::PackageManager::setIgnorePrefs(true); + Rv::Options::manglePerSourceArgs(argv, argc); + Rv::Options prefOpts = opts; + + vector nargv; + TwkApp::DarwinBundle::removePSN(argc, argv, nargv); + + // + // Additional options. These are QT UI look related. + // + + // Required by the Live Review RV plugin React component's scroll bars + std::vector arguments( argv, argv + argc ); + static char enableOverlayScrollbar[] = "--enable-features=OverlayScrollbar"; + arguments.emplace_back( enableOverlayScrollbar ); + argc = static_cast( arguments.size() ); + argv = &arguments[0]; +; + + // + // Initialze IMF library for multi-threading + // + + // + // Call the deploy functions + // + + TWK_DEPLOY_APP_OBJECT dobj(MAJOR_VERSION, + MINOR_VERSION, + REVISION_NUMBER, + argc, argv, + RELEASE_DESCRIPTION, + "HEAD=" GIT_HEAD); + + Imf::staticInitialize(); + TwkFB::GenericIO::init(); // Initialize TwkFB::GenericIO plugins statics + TwkMovie::GenericIO::init(); // Initialize TwkMovie::GenericIO plugins statics + + // + // We handle the '--help' flag by changing it to '-help' for + // argparse to work. + // + for (int i = 0; i < argc; ++i) + { + if (!strcmp("--help", argv[i])) + { + strcpy(argv[i], "-help"); + break; + } + } + + // + // Parse cmd line args + // + + IPCore::Application::cacheEnvVars(); + + int strictlicense = 0; + int registerHandler = 0; + char *prefsPath; + int sleepTime = 0; + + if (arg_parse + (nargv.size(), &nargv.front(), + "", "", + RV_ARG_EXAMPLES, + "", "", + RV_ARG_SEQUENCE_HELP, + "", "", + RV_ARG_SOURCE_OPTIONS(opts), + "", "", + "", ARG_SUBR(Rv::RvApplication::parseInFiles), "Input sequence patterns, images, movies, or directories ", + RV_ARG_PARSE_OPTIONS(opts), + "-strictlicense", ARG_FLAG(&strictlicense), "Exit rather than consume an RV license if no rvsolo licenses are available", + "-prefsPath %S", &prefsPath, "Alternate path to preferences directory", + "-registerHandler", ARG_FLAG(®isterHandler), "Register this executable as the default rvlink protocol handler (OS X only)", + "-rt %d %d %d %d", &rtPeriod, &rtComputation, &rtConstraint, &rtPreemptable, "Real time parameters (default=%d %d %d %d)", rtPeriod, rtComputation, rtConstraint, rtPreemptable, + "-sleep %d", &sleepTime, "Sleep (in seconds) before starting to allow attaching debugger", + NULL) < 0) + { + exit(-1); + } + + if (sleepTime > 0) + { + cout << "INFO: sleeping " << sleepTime << " seconds" << endl; + std::this_thread::sleep_for( std::chrono::seconds( sleepTime ) ); + cout << "INFO: continuing after sleep" << endl; + } + + if (opts.showVersion) + { + cout << MAJOR_VERSION << "." << MINOR_VERSION << "." << REVISION_NUMBER << endl; + exit(0); + } + + // + // Thread scheduling + // + + thread_priority_init(); + + // + // Desktop aware: being "desktop aware" may be causing problems + // on some systems + // + + QApplication::setDesktopSettingsAware(opts.qtdesktop == 1); + + // + // Banners + // + + TWK_DEPLOY_SHOW_PROGRAM_BANNER(cout); + TWK_DEPLOY_SHOW_COPYRIGHT_BANNER(cout); + TWK_DEPLOY_SHOW_LOCAL_BANNER(cout); + + // + // Get CPU info and tell the EXR library to use them all + // + + if (opts.exrcpus > 0) + { + Imf::setGlobalThreadCount(opts.exrcpus); + } + else + { + Imf::setGlobalThreadCount(TwkUtil::SystemInfo::numCPUs() > 1 + ? (TwkUtil::SystemInfo::numCPUs()-1) + : 1); + } + + // + // Application + // + + QApplication* app = new QApplication(argc, argv); + + QTranslator* translator = new QTranslator(); + QLocale locale = QLocale(getenv("ORIGINALLOCAL")); + if (translator->load(locale, QLatin1String("i18n"), "_",QLatin1String(":/translations"))) { + app->installTranslator(translator); + } + + + #ifdef PLATFORM_DARWIN + // + // As of Qt 4.7, the qt.conf file in Contents/Resources no longer + // seems to get Qt to look in our plugins dir. As a result plugins + // can be loaded from /Developer, etc, and chaos ensues. The below + // seems to do the trick. + // + QDir dir(QCoreApplication::applicationDirPath()); + dir.cdUp(); + dir.cd("PlugIns"); + dir.cd("Qt"); + QCoreApplication::addLibraryPath(dir.absolutePath()); + + dir.cdUp(); + dir.cdUp(); + dir.cd("MacOS"); + QCoreApplication::removeLibraryPath(dir.absolutePath()); + QCoreApplication::removeLibraryPath("/Developer/Applications/Qt/plugins"); + #endif + + Rv::RvApplication* rvapp = new Rv::RvApplication(argc, argv, createDevices); + app->installEventFilter(rvapp); + app->setQuitOnLastWindowClosed(true); + + // + // Get the bundle info so we can find resource files + // + // init priorities are: + // + // 1) -init script + // 2) $RV_INIT + // 3) $HOME/.rvrc.mu + // 4) $RV_HOME/scripts/rv/rvrc.mu + // 5) ./rvrc.mu + // + + if (getenv("RV_APP_RVIO")) bundle.setEnvVar("RV_APP_RVIO_SET_BY_USER", "true"); + else bundle.setEnvVar("RV_APP_RVIO", bundle.executableFile("rvio")); + + bundle.setEnvVar("RV_APP_RV", bundle.executableFile("RV")); + bundle.setEnvVar("RV_APP_RV_SHORT_NAME", "RV"); + bundle.setEnvVar("RV_APP_MANUAL", bundle.resource("rv_manual", "pdf")); + bundle.setEnvVar("RV_APP_MANUAL_HTML", bundle.resource("rv_manual", "html")); + bundle.setEnvVar("RV_APP_SDI_MANUAL", bundle.resource("rvsdi_manual", "pdf")); + bundle.setEnvVar("RV_APP_SDI_MANUAL_HTML", bundle.resource("rvsdi_manual", "html")); + bundle.setEnvVar("RV_APP_REFERENCE_MANUAL", bundle.resource("rv_reference", "pdf")); + bundle.setEnvVar("RV_APP_REFERENCE_MANUAL_HTML", bundle.resource("rv_reference", "html")); + bundle.setEnvVar("RV_APP_MU_MANUAL", bundle.resource("mu", "pdf")); + bundle.setEnvVar("RV_APP_GTO_REFERENCE", bundle.resource("gto", "pdf")); + bundle.setEnvVar("RV_APP_RELEASE_NOTES", bundle.resource("rv_release_notes", "html")); + bundle.setEnvVar("RV_APP_LICENSES_NOTES", bundle.resource("rv_client_licenses", "html")); + bundle.addPathToEnvVar("OIIO_LIBRARY_PATH", bundle.appPluginPath("OIIO")); + TwkApp::Bundle::PathVector licfiles = bundle.licenseFiles("license", "gto"); + + // + // Find the init file + // + + string muInitFile = bundle.rcfile("rvrc", "mu", "RV_INIT"); + string pyInitFile = bundle.rcfile("rvrc", "py", "RV_PYINIT"); + bundle.setEnvVar("RV_APP_INIT", muInitFile.c_str()); + bundle.setEnvVar("RV_APP_PYINIT", pyInitFile.c_str()); + + if (! opts.licarg) opts.licarg = getenv("RV_LICENSE_FILE"); + if (! opts.licarg) opts.licarg = getenv("TWEAK_LICENSE_FILE"); + + if (opts.licarg) + { + // + // Override license file from command line + // + + QFileInfo qfi(opts.licarg); + if (!qfi.isReadable()) + { + cerr << "ERROR: license file '" << opts.licarg << "' unreadable" << endl; + exit(-1); + } + licfiles.resize(1); + licfiles.front() = opts.licarg; + bundle.setEnvVar("RV_APP_USE_LICENSE_FILE", opts.licarg); + } + + try + { + //TwkFB::GenericIO::loadPlugins("TWK_FB_PLUGIN_PATH"); + TwkFB::loadProxyPlugins("TWK_FB_PLUGIN_PATH"); + TwkMovie::loadProxyPlugins("TWK_MOVIE_PLUGIN_PATH"); + } + catch (...) + { + cerr << "WARNING: a problem occured while loading image plugins." << endl; + cerr << " some plugins may not have been loaded." << endl; + } + + TwkMovie::GenericIO::addPlugin(new MovieFBIO()); + TwkMovie::GenericIO::addPlugin(new MovieProceduralIO()); + + // + // Compile the list of sequencable file extensions + // + + TwkFB::GenericIO::compileExtensionSet(predicateFileExtensions()); + + // + // QT starts here + // + + if (!getenv("RV_DARK")) bundle.setEnvVar("RV_DARK", "", true); + + QString csstext; + + if (opts.qtcss) + { + string s = scarfFile(opts.qtcss); + csstext = s.c_str(); + } + else + { + csstext = QString(rv_mac_dark) + .arg(opts.fontSize1) + .arg(opts.fontSize2); + } + + if (!opts.qtstyle || !strcmp(opts.qtstyle, "RV")) + { + bundle.setEnvVar("RV_DARK", "1", true); + } + + app->setStyleSheet(csstext); + + try + { + TwkApp::initMu(0, gc_filter); + TwkApp::initPython(argc, argv); + Rv::initCommands(); + Rv::initUICommands(); + + if (!opts.initializeAfterParsing((noPrefs) ? 0 : &prefOpts)) + { + rvapp->console()->processLastTextBuffer(); + exit(-1); + } + + TwkApp::initWithFile(TwkApp::muContext(), + TwkApp::muProcess(), + TwkApp::muModuleList(), + muInitFile.c_str()); + + TwkApp::pyInitWithFile(pyInitFile.c_str(), Rv::pyRvAppCommands(), Rv::pyUICommands()); + } + catch (const exception &e) + { + cerr << "ERROR: during initialization: " << e.what() << endl; + rvapp->console()->processLastTextBuffer(); + exit( -1 ); + } + + rvapp->console()->processTextBuffer(); + + bool dorun = true; + + rvapp->createNewSessionFromFiles(opts.inputFiles); + + rvapp->processNetworkOpts(); + + int rval = 0; + + // + // Overwrite python's signal handler + // + + if (signal(SIGINT, control_c_handler) == SIG_ERR) + { + cout << "ERROR: failed to install SIGINT signal handler" << endl; + } + + TwkUtil::setThreadName("RV Main"); + + if (dorun) + { + try + { + #ifdef PLATFORM_DARWIN + // + // As of Qt 4.7, they have moved their event handler registration deep + // into the event loop so it's not possible to override it. Turns out + // that if we set this attribute at exactly this moment, it prevents the + // handler registration overriding without any other bad effects. We + // hope. XXX + // + QCoreApplication::setAttribute (Qt::AA_DontUseNativeMenuBar, false); + #endif + rval = app->exec(); + } + catch (TwkExc::Exception& exc) + { + cerr << exc; + exit(-1); + } + catch (const exception &e) + { + cerr << "ERROR: Unhandled exception during execution: " + << e.what() + << endl; + + exit(-1); + } + catch (...) + { + cerr << "ERROR: Unhandled unknown exception" << endl; + exit(-1); + } + } + + TwkMovie::GenericIO::shutdown(); // Shutdown TwkMovie::GenericIO plugins + TwkFB::GenericIO::shutdown(); // Shutdown TwkFB::GenericIO plugins + TwkFB::ThreadPool::shutdown(); // Shutdown TwkFB ThreadPool + + TwkGLF::UninitPBOPools(); + + // Ensure to delete the QApplication before calling finalizePython + delete rvapp; + delete app; + TwkApp::finalizePython(); + + return rval; +} diff --git a/src/bin/nsapps/RV/rvdir.icns b/src/bin/nsapps/RV/rvdir.icns new file mode 100644 index 000000000..092ce1bda Binary files /dev/null and b/src/bin/nsapps/RV/rvdir.icns differ diff --git a/src/bin/nsapps/RV/rvfile.icns b/src/bin/nsapps/RV/rvfile.icns new file mode 100644 index 000000000..092ce1bda Binary files /dev/null and b/src/bin/nsapps/RV/rvfile.icns differ diff --git a/src/bin/python/CMakeLists.txt b/src/bin/python/CMakeLists.txt new file mode 100644 index 000000000..190f3cfde --- /dev/null +++ b/src/bin/python/CMakeLists.txt @@ -0,0 +1,7 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +ADD_SUBDIRECTORY(py-interp) diff --git a/src/bin/python/py-interp/CMakeLists.txt b/src/bin/python/py-interp/CMakeLists.txt new file mode 100644 index 000000000..ff9b3371f --- /dev/null +++ b/src/bin/python/py-interp/CMakeLists.txt @@ -0,0 +1,75 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "py-interp" +) + +FIND_PACKAGE( + Qt5 + COMPONENTS Core Gui Widgets + REQUIRED +) + +FILE(GLOB _sources main.cpp) + +ADD_EXECUTABLE( + ${_target} + ${_sources} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} +) + +# The main.cpp source use its own definitions +TARGET_COMPILE_OPTIONS( + ${_target} + PRIVATE -DBUNDLE_MAJOR_VERSION=${RV_MAJOR_VERSION} -DBUNDLE_MINOR_VERSION=${RV_MINOR_VERSION} -DBUNDLE_REVISION_NUMBER=${RV_REVISION_NUMBER} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE Python::Python Boost::filesystem Boost::system Qt5::Core Qt5::Gui Qt5::Widgets +) + +IF(RV_TARGET_DARWIN) + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE DarwinBundle + ) +ELSE() + TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE QTBundle + ) +ENDIF() + +RV_STAGE(TYPE "EXECUTABLE_WITH_PLUGINS" TARGET ${_target}) + +ADD_CUSTOM_COMMAND( + COMMENT "Generating .pyc for ${RV_STAGE_PLUGINS_PYTHON_DIR}" + OUTPUT ${RV_STAGE_PLUGINS_PYTHON_DIR}/.compiled + COMMAND ${CMAKE_COMMAND} -E env QT_QPA_PLATFORM=minimal $ -m compileall -f ${RV_STAGE_PLUGINS_PYTHON_DIR} + COMMAND ${CMAKE_COMMAND} -E touch ${RV_STAGE_PLUGINS_PYTHON_DIR}/.compiled + DEPENDS ${_target} python_source_modules installed_packages +) + +ADD_CUSTOM_TARGET( + pyc_generation ALL + DEPENDS ${RV_STAGE_PLUGINS_PYTHON_DIR}/.compiled +) + +ADD_DEPENDENCIES(compiled_python_source_modules pyc_generation) + +ADD_TEST( + NAME "py-interp ${python_test_file_name}" + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND ${CMAKE_COMMAND} -E env QT_QPA_PLATFORM=minimal "$" -m unittest --verbose +) diff --git a/src/bin/python/py-interp/main.cpp b/src/bin/python/py-interp/main.cpp new file mode 100644 index 000000000..7ef62e4b4 --- /dev/null +++ b/src/bin/python/py-interp/main.cpp @@ -0,0 +1,67 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +/* Minimal main program -- everything is loaded from the library */ + +#include +#ifdef PLATFORM_DARWIN +#include +#else +#include +#endif +#include +#include +#include + +#include + +using namespace std; +using namespace TwkApp; + +#ifdef PLATFORM_DARWIN +DarwinBundle bundle("python", BUNDLE_MAJOR_VERSION, BUNDLE_MINOR_VERSION, BUNDLE_REVISION_NUMBER); +#else +QTBundle bundle("python", BUNDLE_MAJOR_VERSION, BUNDLE_MINOR_VERSION, BUNDLE_REVISION_NUMBER); +#endif + +int +main(int argc, char **argv) +{ + //Sleep(10 * 1000); + + QApplication app(argc, argv); +#ifndef PLATFORM_DARWIN + bundle.initializeAfterQApplication(); // cause init() to be called +#endif + + Py_InitializeEx( 1 ); + Py_SetProgramName( Py_DecodeLocale( argv[0], nullptr ) ); + + static wchar_t delim = L'\0'; + + wchar_t** w_argv = new wchar_t*[argc + 1]; + w_argv[argc] = &delim; + + for (int i = 0; i < argc; ++i ){ + w_argv[i] = Py_DecodeLocale(argv[i], nullptr); + } + + PySys_SetArgvEx( argc, w_argv, 0 ); + +#ifdef PLATFORM_WINDOWS + // + // On windows: force -i + // + wchar_t* w_nargv[] = {Py_DecodeLocale(argv[0], nullptr), L"-i", L'\0'}; + + if (argc == 1) + return Py_Main(2, w_nargv); + else + return Py_Main(argc, w_argv); +#else + + return Py_Main(argc, w_argv); +#endif +} diff --git a/src/bin/python/py-interp/py-interp.wrapper b/src/bin/python/py-interp/py-interp.wrapper new file mode 100644 index 000000000..dbe2e764a --- /dev/null +++ b/src/bin/python/py-interp/py-interp.wrapper @@ -0,0 +1,47 @@ +#!/bin/tcsh -f +# +# This script sets the RV_HOME environment (if not already set), +# makes sure the plugins are the path and launches the actual binary. +# +# NOTE: doesn't properly set RV_HOME if invoked from a link to the script +# and RV_HOME is not already set +# + +set noglob + +# +# Uncomment this if you're on an older linux distro and RV is hanging in +# the Audio preferences or on startup with audio sources +# +#setenv PA_ALSA_EXCLUDE_DMIX_DEFAULT 1 + +# +# For unknown reasons, LANG causes problems when set to +# interesting values (like fr_FR.UTF-8). +# +unsetenv LANG + +if (! $?RV_HOME) then + set canonicalName = "`readlink -f $0`" + set binName = "$canonicalName:h" + setenv RV_HOME "$binName:h" +endif + +set platform = i386 +set pyinterpbin = "$RV_HOME/bin/py-interp.bin.$platform" + +if (! (-e $pyinterpbin) ) then + set pyinterpbin = "$RV_HOME/bin/py-interp.bin" +endif + +setenv PATH "$RV_HOME/bin:${PATH}" + +if ($?LD_LIBRARY_PATH) then + setenv LD_LIBRARY_PATH "$RV_HOME/lib:$LD_LIBRARY_PATH" +else + setenv LD_LIBRARY_PATH "$RV_HOME/lib" +endif + +# exec RV + +exec $pyinterpbin $*:q diff --git a/src/bin/python/py-interp/tests/__init__.py b/src/bin/python/py-interp/tests/__init__.py new file mode 100644 index 000000000..47f232233 --- /dev/null +++ b/src/bin/python/py-interp/tests/__init__.py @@ -0,0 +1,16 @@ +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +import os +import unittest + + +class TestBaseSetup(unittest.TestCase): + def test_qt_env_var(self): + qt_platform = os.environ.get("QT_QPA_PLATFORM") + self.assertIsNotNone( + qt_platform, "Was expecting the QT_QPA_PLATFORM environment variable" + ) + self.assertEqual("minimal", qt_platform) diff --git a/src/bin/python/py-interp/tests/test_PySide2.py b/src/bin/python/py-interp/tests/test_PySide2.py new file mode 100644 index 000000000..42a705ef9 --- /dev/null +++ b/src/bin/python/py-interp/tests/test_PySide2.py @@ -0,0 +1,20 @@ +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +import unittest +import PySide2 +from PySide2 import * + + +class TestPySide2(unittest.TestCase): + def test_getting_qt_instance(self): + self.assertIsNotNone( + QtCore.QCoreApplication.instance(), + "We should have an application defined from py-interp's qt bindings.", + ) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/bin/python/py-interp/tests/test_SSL.py b/src/bin/python/py-interp/tests/test_SSL.py new file mode 100755 index 000000000..c238638f6 --- /dev/null +++ b/src/bin/python/py-interp/tests/test_SSL.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python + +# ***************************************************************************** +# Copyright (c) 2020 Autodesk, Inc. +# All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# ***************************************************************************** + + +def _fetch(name, func, url): + try: + func(url) + except Exception as e: + print(f"[{name}]: Unable to fetch {url} => {e}") + return False + + print(f"[{name}]: Successfully fetched {url}".format(name=name, url=url)) + return True + + +def fetch_httplib(url): + def impl(url): + from six.moves import http_client + from six.moves import urllib_parse + + u = urllib_parse.urlparse(url) + http_client.HTTPSConnection(u.netloc).request("GET", u.path) + + return _fetch("httplib", impl, url) + + +def fetch_urllib(url): + def impl(url): + from six.moves import urllib + + urllib.request.urlopen(url).read() + + return _fetch("urllib", impl, url) + + +def fetch_requests(url): + def impl(url): + import requests + + requests.get(url).raise_for_status() + + return _fetch("requests", impl, url) + + +app = None + + +def fetch_pyside(url): + def impl(url): + from PySide2.QtCore import QEventLoop, QUrl, QCoreApplication + from PySide2.QtNetwork import ( + QNetworkRequest, + QNetworkReply, + QNetworkAccessManager, + ) + + global app + if app is None: + app = QCoreApplication.instance() or QCoreApplication() + + event_loop = QEventLoop() + + manager = QNetworkAccessManager() + manager.finished[QNetworkReply].connect(lambda: event_loop.exit(0)) + + reply = manager.get(QNetworkRequest(QUrl(url))) + event_loop.exec_() + + if reply.error() != QNetworkReply.NoError: + raise AssertionError(reply.errorString()) + + return _fetch("PySide2.QNetwork.QNetworkRequest", impl, url) + + +def fetch_all(url, is_valid_url): + results = list() + + results.append(fetch_httplib(url)) + results.append(fetch_urllib(url)) + results.append(fetch_requests(url)) + results.append(fetch_pyside(url)) + + if is_valid_url is True and all(results) is False: + raise AssertionError(f"FAILED: One of the libraries failed to fetch {url}") + + elif is_valid_url is False and any(results) is True: + raise AssertionError( + f"FAILED: One of the libraries failed to raise an error for {url}" + ) + + print(f" - All libraries behaved well on {url}\n") + + +import unittest + + +class TestSSL(unittest.TestCase): + def test_good_urls(self): + # Source: https://aws.amazon.com/blogs/security/how-to-prepare-for-aws-move-to-its-own-certificate-authority/ + good_url = [ + "https://good.sca1a.amazontrust.com/", + "https://good.sca2a.amazontrust.com/", + "https://good.sca3a.amazontrust.com/", + "https://good.sca4a.amazontrust.com/", + "https://good.sca0a.amazontrust.com/", + ] + for url in good_url: + fetch_all(url, True) + + def test_bad_urls(self): + bad_url = ["https://untrusted-root.badssl.com/"] + + for url in bad_url: + fetch_all(url, False) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/bin/python/py-interp/tests/test_misc_failures.py b/src/bin/python/py-interp/tests/test_misc_failures.py new file mode 100644 index 000000000..73aa669ca --- /dev/null +++ b/src/bin/python/py-interp/tests/test_misc_failures.py @@ -0,0 +1,29 @@ +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +""" +Simple tests asserting that our testing setup +does report failures. +""" +import unittest + + +class TestMiscFailures(unittest.TestCase): + def test_importing_invalid_package(self): + with self.assertRaises(ImportError): + import non_existing_package + + # def test_invalid_syntax(self): + # with self.assertRaises(SyntaxError): + # if(True) print "Hello from IF Python!" + # else print "Hello from ELSE Python !" + + def test_calling_non_existing_method(self): + with self.assertRaises(NameError): + pirnt("Hello from Python!") + + +if __name__ == "__main__": + unittest.main() diff --git a/src/bin/python/py-interp/tests/test_rv_package.py b/src/bin/python/py-interp/tests/test_rv_package.py new file mode 100644 index 000000000..bdfa4f164 --- /dev/null +++ b/src/bin/python/py-interp/tests/test_rv_package.py @@ -0,0 +1,19 @@ +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +""" +Simple test just asserting that our application +setup is allowing the import of RV's main package. +""" +import unittest + + +class TestRvPackage(unittest.TestCase): + def test_importing_rv_package(self): + import rv + + +if __name__ == "__main__": + unittest.main() diff --git a/src/bin/utf8Main.cpp b/src/bin/utf8Main.cpp new file mode 100644 index 000000000..64a268b56 --- /dev/null +++ b/src/bin/utf8Main.cpp @@ -0,0 +1,51 @@ +//*****************************************************************************/ +// Copyright (c) 2018 Autodesk, Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//*****************************************************************************/ + +#if defined( _WIN32 ) +#include +#include +#endif + + +int utf8Main( int argc, char* argv[] ); + +#if defined( _WIN32 ) +int wmain(int argc, wchar_t* wargv[]) +{ + // converts UCS2 arguements to utf-8 charset + char ** argv = new char * [argc]; + for (int argn = 0; argn < argc; argn++) + { + int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, wargv[argn], -1, NULL, 0, NULL, NULL); + if (sizeNeeded > 0 ) + { + char * strTo = new char [sizeNeeded]; + WideCharToMultiByte(CP_UTF8, 0, wargv[argn], -1, strTo, sizeNeeded, NULL, NULL); + argv[argn] = strTo; + } + else + argv[argn] = nullptr; + } + + int result = utf8Main(argc, argv); + + for (int argn = 0; argn < argc; argn++) + delete [] argv[argn]; + + delete [] argv; + + return result; +} + +#else +int main(int argc, char* argv[]) +{ + return utf8Main(argc, argv); +} +#endif + diff --git a/src/bin/utf8Main.h b/src/bin/utf8Main.h new file mode 100644 index 000000000..31f4a4b09 --- /dev/null +++ b/src/bin/utf8Main.h @@ -0,0 +1,9 @@ +//*****************************************************************************/ +// Copyright (c) 2018 Autodesk, Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//*****************************************************************************/ + +int utf8Main( int argc, char* argv[] ); diff --git a/src/bin/utf8WinMain.cpp b/src/bin/utf8WinMain.cpp new file mode 100644 index 000000000..4c030d9d0 --- /dev/null +++ b/src/bin/utf8WinMain.cpp @@ -0,0 +1,60 @@ +//*****************************************************************************/ +// Copyright (c) 2018 Autodesk, Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//*****************************************************************************/ + +#if defined( _WIN32 ) +#include +#include +#endif + + +int utf8Main( int argc, char* argv[] ); + +#if defined( _WIN32 ) +int wmain(int argc, wchar_t* wargv[]) +{ + // converts UCS2 arguements to utf-8 charset + char ** argv = new char * [argc]; + for (int argn = 0; argn < argc; argn++) + { + int sizeNeeded = WideCharToMultiByte(CP_UTF8, 0, wargv[argn], -1, NULL, 0, NULL, NULL); + if (sizeNeeded > 0 ) + { + char * strTo = new char [sizeNeeded]; + WideCharToMultiByte(CP_UTF8, 0, wargv[argn], -1, strTo, sizeNeeded, NULL, NULL); + argv[argn] = strTo; + } + else + argv[argn] = nullptr; + } + + int result = utf8Main(argc, argv); + + for (int argn = 0; argn < argc; argn++) + delete [] argv[argn]; + + delete [] argv; + + return result; +} + +extern "C" int APIENTRY WinMain(HINSTANCE /*hInstance*/, HINSTANCE /*hPrevInstance*/, LPSTR /*cmdParamarg*/, int /* cmdShow */) +{ + int argc; + wchar_t **argvW = CommandLineToArgvW(GetCommandLineW(), &argc); + if (!argvW) + return -1; + return wmain(argc, argvW); +} + +#else +int main(int argc, char* argv[]) +{ + return utf8Main(argc, argv); +} +#endif + diff --git a/src/build/__init__.py b/src/build/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/build/make_openssl.py b/src/build/make_openssl.py new file mode 100644 index 000000000..b0811b3e9 --- /dev/null +++ b/src/build/make_openssl.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ***************************************************************************** +# Copyright 2020 Autodesk, Inc. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# ***************************************************************************** + +import argparse +import glob +import os +import pathlib +import shutil +import subprocess +import platform +import tempfile +import uuid +from typing import List + +SOURCE_DIR = "" +OUTPUT_DIR = "" +ARCH = "" +PERL_ROOT = "" + +def get_openssl_args(root) -> List[str]: + """ + Return the path to the openssl binary given a package root. + + :param root: OpenSSL package root + + :return: Path to the openssl binary + """ + openssl_bins = glob.glob( + os.path.join(root, "**", "bin", "openssl*"), recursive=True + ) + + print(openssl_bins) + + if not openssl_bins or os.path.exists(openssl_bins[0]) is False: + raise FileNotFoundError() + + print(f"Found openssl binaries {openssl_bins}") + + openssl_bin = sorted(openssl_bins)[0] + + return [openssl_bin] + + +def patch_openssl_distribution() -> None: + """ + Patch the OpenSSL package so it can be used standalone. + """ + + if platform.system() == "Darwin": + print("Making the shared libraries relocatable") + + openssl_libs = glob.glob(os.path.join(OUTPUT_DIR, "lib", "lib*.dylib*")) + openssl_libs = [lib for lib in openssl_libs if os.path.islink(lib) is False] + + openssl_bin = get_openssl_args(OUTPUT_DIR)[0] + + for rpath in ["@executable_path/../lib", "@executable_path/."]: + print(f"Adding {rpath} as an rpath to the openssl binary") + install_name_tool_add_args = [ + "install_name_tool", + "-add_rpath", + rpath, + openssl_bin, + ] + + print(f"Executing {install_name_tool_add_args}") + subprocess.run(install_name_tool_add_args).check_returncode() + + for lib_path in openssl_libs: + lib_name = os.path.basename(lib_path) + + print(f"Setting the 'id' to {lib_name}") + install_name_tool_id_args = [ + "install_name_tool", + "-id", + f"@rpath/{lib_name}", + lib_path, + ] + + print(f"Executing {install_name_tool_id_args}") + subprocess.run(install_name_tool_id_args).check_returncode() + + print(f"Changing the library path of {lib_name} in the OpenSSL binary") + install_name_tool_change_args = [ + "install_name_tool", + "-change", + lib_path, + f"@rpath/{lib_name}", + openssl_bin, + ] + + print(f"Executing {install_name_tool_change_args}") + subprocess.run(install_name_tool_change_args).check_returncode() + + for dependencies_path in openssl_libs: + if dependencies_path == lib_path: + continue + + dependencies_name = os.path.basename(dependencies_path) + + print(f"Changing the library path of {lib_name} in {dependencies_name}") + install_name_tool_change_args = [ + "install_name_tool", + "-change", + dependencies_path, + f"@rpath/{dependencies_name}", + lib_path, + ] + + print(f"Executing {install_name_tool_change_args}") + subprocess.run(install_name_tool_change_args).check_returncode() + + +def test_openssl_distribution() -> None: + """ + Test the OpenSSL distribution. + """ + tmp_dir = os.path.join(tempfile.gettempdir(), str(uuid.uuid4())) + os.makedirs(tmp_dir) + + tmp_openssl_dir = os.path.join(tmp_dir, "OpenSSL") + + try: + print(f"Moving {OUTPUT_DIR} to {tmp_openssl_dir}") + shutil.move(OUTPUT_DIR, tmp_openssl_dir) + + openssl_bin = get_openssl_args(tmp_openssl_dir) + + print(f"Executing {openssl_bin}") + subprocess.run( + openssl_bin, + input=b"exit\n", + ).check_returncode() + finally: + print(f"Moving {tmp_openssl_dir} to {OUTPUT_DIR}") + shutil.move(tmp_openssl_dir, OUTPUT_DIR) + + +def clean() -> None: + """ + Run the clean step of the build. Removes everything. + """ + + if os.path.exists(OUTPUT_DIR): + shutil.rmtree(OUTPUT_DIR) + + git_clean_command = ["git", "clean", "-dxff"] + + print(f"Executing {git_clean_command} from {SOURCE_DIR}") + subprocess.run(git_clean_command, cwd=SOURCE_DIR) + + +def configure() -> None: + """ + Run the configure step of the build. It builds the makefile required to build OpenSSL with the path correctly set. + """ + if os.path.exists(OUTPUT_DIR) is not True: + os.makedirs(OUTPUT_DIR) + + configure_args = [ + os.path.join(PERL_ROOT, "perl"), + os.path.join(SOURCE_DIR, "Configure"), + ] + + if ARCH: + configure_args = ["arch", ARCH] + configure_args + + if platform.system() == "Darwin": + if ARCH: + configure_args.append(f"darwin64{ARCH}-cc") + else: + configure_args.append("darwin64-x86_64-cc") + elif platform.system() == "Windows": + configure_args.append("VC-WIN64A") + else: + configure_args.append("linux-x86_64") + + configure_args.append("no-asm") + configure_args.append(f"--prefix={OUTPUT_DIR}") + configure_args.append(f"--openssldir={OUTPUT_DIR}") + + if platform.system() == "Linux": + configure_args.append("-Wl,-rpath,'$$ORIGIN/../lib'") + + print(f"Executing {configure_args} from {SOURCE_DIR}") + + subprocess.run(configure_args, cwd=SOURCE_DIR).check_returncode() + + +def build() -> None: + """ + Run the build step of the build. It compile every target of the project. + """ + if platform.system() == "Windows": + build_args = ["nmake"] + else: + build_args = ["make", f"-j{os.cpu_count() or 1}"] + + if ARCH: + build_args = ["arch", ARCH] + build_args + + print(f"Executing {build_args} from {SOURCE_DIR}") + subprocess.run(build_args, cwd=SOURCE_DIR).check_returncode() + + +def install() -> None: + """ + Run the install step of the build. It puts all the files inside of the output directory and make them ready to be + packaged. + """ + if platform.system() == "Windows": + install_args = ["nmake", "install_sw"] + else: + install_args = ["make", "install_sw", f"-j{os.cpu_count() or 1}"] + + if ARCH: + install_args = ["arch", ARCH] + install_args + + print(f"Executing {install_args} from {SOURCE_DIR}") + subprocess.run(install_args, cwd=SOURCE_DIR).check_returncode() + + patch_openssl_distribution() + test_openssl_distribution() + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + + parser.add_argument("--clean", dest="clean", action="store_true") + parser.add_argument("--configure", dest="configure", action="store_true") + parser.add_argument("--build", dest="build", action="store_true") + parser.add_argument("--install", dest="install", action="store_true") + + parser.add_argument("--source-dir", dest="source", type=pathlib.Path, required=True) + parser.add_argument("--output-dir", dest="output", type=pathlib.Path, required=True) + + parser.add_argument("--arch", dest="arch", type=str, required=False, default="") + parser.add_argument("--perlroot", dest="perlroot", type=str, required=False, default="") + + parser.set_defaults(clean=False, configure=False, build=False, install=False) + + args = parser.parse_args() + print(args) + + SOURCE_DIR = args.source + OUTPUT_DIR = args.output + ARCH = args.arch + PERL_ROOT = args.perlroot + + if args.clean: + clean() + + if args.configure: + configure() + + if args.build: + build() + + if args.install: + install() diff --git a/src/build/make_pyside.py b/src/build/make_pyside.py new file mode 100755 index 000000000..9fef3edec --- /dev/null +++ b/src/build/make_pyside.py @@ -0,0 +1,283 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ***************************************************************************** +# Copyright 2020 Autodesk, Inc. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# ***************************************************************************** + +import argparse +import glob +import os +import pathlib +import shutil +import subprocess +import platform +import tempfile +import uuid + +from utils import ( + download_file, + extract_7z_archive, + verify_7z_archive, + source_widows_msvc_env, + update_env_path, +) +from make_python import get_python_interpreter_args + +SOURCE_DIR = "" +OUTPUT_DIR = "" +TEMP_DIR = "" +VARIANT = "" + +QT_OUTPUT_DIR = "" +PYTHON_OUTPUT_DIR = "" +OPENSSL_OUTPUT_DIR = "" + +LIBCLANG_URL = { + "Darwin": "https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_80-based-mac.7z", + "Linux": "https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_80-based-linux-Rhel7.2-gcc5.3-x86_64.7z", + "Windows": "https://download.qt.io/development_releases/prebuilt/libclang/libclang-release_80-based-windows-vs2017_64.7z", +} + + +def test_python_distribution(python_home: str) -> None: + """ + Test the Python distribution. + + :param python_home: Package root of an Python package + """ + tmp_dir = os.path.join(tempfile.gettempdir(), str(uuid.uuid4())) + os.makedirs(tmp_dir) + + tmp_python_home = os.path.join(tmp_dir, os.path.basename(python_home)) + try: + print(f"Moving {python_home} to {tmp_python_home}") + shutil.move(python_home, tmp_python_home) + + python_validation_args = get_python_interpreter_args(tmp_python_home) + [ + "-c", + "\n".join( + [ + "from PySide2 import *", + "from PySide2 import QtWebEngineCore, QtWebEngine", + ] + ), + ] + print(f"Validating the PySide package with {python_validation_args}") + subprocess.run(python_validation_args).check_returncode() + finally: + print(f"Moving {tmp_python_home} to {python_home}") + shutil.move(tmp_python_home, python_home) + + +def prepare() -> None: + """ + Run the clean step of the build. Removes everything. + """ + if os.path.exists(TEMP_DIR) is False: + os.makedirs(TEMP_DIR) + + libclang_url = LIBCLANG_URL[platform.system()] + libclang_zip = os.path.join(TEMP_DIR, "libclang.7z") + if os.path.exists(libclang_zip) is False: + download_file(libclang_url, libclang_zip) + # if we have a failed download, clean it up and redownload. + elif not verify_7z_archive(libclang_zip): + os.remove(libclang_zip) + download_file(libclang_url, libclang_zip) + + # clean up previous failed extraction + libclang_tmp = os.path.join(TEMP_DIR, "libclang-tmp") + if os.path.exists(libclang_tmp) is True: + shutil.rmtree(libclang_tmp) + + # extract to a temp location and only move if successful + libclang_extracted = os.path.join(TEMP_DIR, "libclang") + if os.path.exists(libclang_extracted) is False: + extract_7z_archive(libclang_zip, libclang_tmp) + shutil.move(libclang_tmp, libclang_extracted) + + libclang_install_dir = os.path.join(libclang_extracted, "libclang") + + os.environ["PATH"] = os.path.pathsep.join( + [ + os.path.join(OPENSSL_OUTPUT_DIR, "bin"), + os.environ.get("PATH", ""), + ] + ) + + print(f"PATH={os.environ['PATH']}") + + os.environ["LLVM_INSTALL_DIR"] = libclang_install_dir + os.environ["CLANG_INSTALL_DIR"] = libclang_install_dir + + # PySide2 build requires a version of numpy lower than 1.23 + install_numpy_args = get_python_interpreter_args(PYTHON_OUTPUT_DIR) + [ + "-m", + "pip", + "install", + "numpy<1.23", + ] + print(f"Installing numpy with {install_numpy_args}") + subprocess.run(install_numpy_args).check_returncode() + + +def remove_broken_shortcuts(python_home: str) -> None: + """ + Remove broken Python shortcuts that depend on the absolute + location of the Python executable. + + Note that this method will also remove scripts like + pip, easy_install and wheel that were left around by + previous steps of the build pipeline. + + :param str python_home: Path to the Python folder. + :param int version: Version of the python executable. + """ + if platform.system() == "Windows": + # All executables inside Scripts have a hardcoded + # absolute path to the python, which can't be relied + # upon, so remove all scripts. + shutil.rmtree(os.path.join(python_home, "Scripts")) + else: + # Aside from the python executables, every other file + # in the build is a script that does not support + bin_dir = os.path.join(python_home, "bin") + for filename in os.listdir(bin_dir): + filepath = os.path.join(bin_dir, filename) + if filename not in [ + "python", + "python3", + "python3.9", + ]: + print(f"Removing {filepath}...") + os.remove(filepath) + else: + print(f"Keeping {filepath}...") + + +def build() -> None: + """ + Run the build step of the build. It compile every target of the project. + """ + python_home = PYTHON_OUTPUT_DIR + python_interpreter_args = get_python_interpreter_args(python_home) + + pyside_build_args = python_interpreter_args + [ + os.path.join(SOURCE_DIR, "setup.py"), + "install", + f"--qmake={os.path.join(QT_OUTPUT_DIR, 'bin', 'qmake' + ('.exe' if platform.system() == 'Windows' else ''))}", + "--ignore-git", + "--standalone", + f"--openssl={os.path.join(OPENSSL_OUTPUT_DIR, 'bin')}", + "--verbose-build", + f"--parallel={os.cpu_count() or 1}", + ] + + # PySide2 v5.15.2.1 builds with errors on Windows using Visual Studio 2019. + # We force Visual Studio 2017 here to make it build without errors. + if platform.system() == "Windows": + source_widows_msvc_env("2017") + + # Add Qt jom to the path to build in parallel + jom_path = os.path.abspath( + os.path.join(QT_OUTPUT_DIR, "..", "..", "Tools", "QtCreator", "bin", "jom") + ) + if os.path.exists(os.path.join(jom_path, "jom.exe")): + print(f"jom.exe was successfully located at: {jom_path}") + update_env_path([jom_path]) + else: + print(f"Could not find jom.exe at the expected location: {jom_path}") + print(f"Build performance might be impacted") + + # Add the debug switch to match build type but only on Windows + # (on other platforms, PySide2 is built in release) + if VARIANT == "Debug": + pyside_build_args.append("--debug") + + print(f"Executing {pyside_build_args}") + subprocess.run(pyside_build_args).check_returncode() + + generator_cleanup_args = python_interpreter_args + [ + "-m", + "pip", + "uninstall", + "-y", + "shiboken2_generator", + ] + + print(f"Executing {generator_cleanup_args}") + subprocess.run(generator_cleanup_args).check_returncode() + + # Even if we remove shiboken2_generator from pip, the files stays... for some reasons + generator_cleanup_args = python_interpreter_args + [ + "-c", + "\n".join( + [ + "import os, shutil", + "try:", + " import shiboken2_generator", + "except:", + " exit(0)", + "shutil.rmtree(os.path.dirname(shiboken2_generator.__file__))", + ] + ), + ] + + print(f"Executing {generator_cleanup_args}") + subprocess.run(generator_cleanup_args).check_returncode() + + if platform.system() == "Windows": + pyside_folder = glob.glob( + os.path.join(python_home, "**", "site-packages", "PySide2"), recursive=True + )[0] + openssl_libs = glob.glob(os.path.join(OPENSSL_OUTPUT_DIR, "bin", "lib*")) + + for lib in openssl_libs: + print(f"Copying {lib} into {pyside_folder}") + shutil.copy(lib, pyside_folder) + + remove_broken_shortcuts(python_home) + test_python_distribution(python_home) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + + parser.add_argument("--prepare", dest="prepare", action="store_true") + parser.add_argument("--build", dest="build", action="store_true") + + parser.add_argument("--source-dir", dest="source", type=pathlib.Path, required=True) + parser.add_argument("--python-dir", dest="python", type=pathlib.Path, required=True) + parser.add_argument("--qt-dir", dest="qt", type=pathlib.Path, required=True) + parser.add_argument( + "--openssl-dir", dest="openssl", type=pathlib.Path, required=True + ) + parser.add_argument("--temp-dir", dest="temp", type=pathlib.Path, required=True) + parser.add_argument("--output-dir", dest="output", type=pathlib.Path, required=True) + + parser.add_argument("--variant", dest="variant", type=str, required=True) + + parser.set_defaults(prepare=False, build=False) + + args = parser.parse_args() + + SOURCE_DIR = args.source + OUTPUT_DIR = args.output + TEMP_DIR = args.temp + OPENSSL_OUTPUT_DIR = args.openssl + PYTHON_OUTPUT_DIR = args.python + QT_OUTPUT_DIR = args.qt + VARIANT = args.variant + + args = parser.parse_args() + print(args) + + if args.prepare: + prepare() + + if args.build: + build() diff --git a/src/build/make_python.py b/src/build/make_python.py new file mode 100755 index 000000000..745bdbe9e --- /dev/null +++ b/src/build/make_python.py @@ -0,0 +1,661 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ***************************************************************************** +# Copyright 2020 Autodesk, Inc. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# ***************************************************************************** + + +import argparse +import glob +import os +import pathlib +import shutil +import sys +import subprocess +import platform +import tempfile +import uuid + +from typing import List +from datetime import datetime + +ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) +sys.path.append(ROOT_DIR) + +OPENSSL_OUTPUT_DIR = "" +SOURCE_DIR = "" +OUTPUT_DIR = "" +TEMP_DIR = "" +VARIANT = "" +ARCH = "" +OPENTIMELINEIO_SOURCE_DIR = "" + +SITECUSTOMIZE_FILE_CONTENT = f''' +# +# Copyright (c) {datetime.now().year} Autodesk, Inc. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +""" +Site-level module that ensures OpenSSL will have up to date certificate authorities +on Linux and macOS. It gets imported when the Python interpreter starts up, both +when launching Python as a standalone interpreter or as an embedded one. +The OpenSSL shipped with Desktop requires a list of certificate authorities to be +distributed with the build instead of relying on the OS keychain. In order to keep +an up to date list, we're going to pull it from the certifi module, which incorporates +all the certificate authorities that are distributed with Firefox. +""" + +try: + import os + import certifi + + # Do not set SSL_CERT_FILE to our own if it is already set. Someone could + # have their own certificate authority that they specify with this env var. + # Unfortunately this is not a PATH like environment variable, so we can't + # concatenate multiple paths with ":". + # + # To learn more about SSL_CERT_FILE and how it is being used by OpenSSL when + # verifying certificates, visit + # https://www.openssl.org/docs/man1.1.0/ssl/SSL_CTX_set_default_verify_paths.html + if "SSL_CERT_FILE" not in os.environ and "DO_NOT_SET_SSL_CERT_FILE" not in os.environ: + os.environ["SSL_CERT_FILE"] = certifi.where() + +except Exception as e: + pass # Silently fail.. + +''' + + +def get_python_interpreter_args(python_home: str) -> List[str]: + """ + Return the path to the python interpreter given a Python home + + :param python_home: Python home of a Python package + :return: Path to the python interpreter + """ + python_interpreters = glob.glob( + os.path.join(python_home, "python*"), recursive=True + ) + python_interpreters += glob.glob(os.path.join(python_home, "bin", "python*")) + + # sort put python# before python#-config + python_interpreters = sorted( + [ + p + for p in python_interpreters + if os.path.islink(p) is False and os.access(p, os.X_OK) + ] + ) + + if not python_interpreters or os.path.exists(python_interpreters[0]) is False: + raise FileNotFoundError() + + print(f"Found python interpreters {python_interpreters}") + + python_interpreter = sorted(python_interpreters)[0] + + return [python_interpreter, "-s", "-E"] + + +def patch_python_distribution(python_home: str) -> None: + """ + Patch the python distribution to make it relocatable. + Patch the python site-packages to rely on certifi to get the SSL certificates. + + :param python_home: Home of the Python to patch. + """ + + for failed_lib in glob.glob( + os.path.join(python_home, "lib", "**", "*_failed.so"), recursive=True + ): + # The Python build mark some so as _failed if it cannot load OpenSSL. In our case it's expected because + # out OpenSSL package works with RPATH and the RPATH is not set on the python build tests. If the lib failed + # for other reasons, it will fail later in our build script. + if "ssl" in failed_lib or "hashlib" in failed_lib: + print(f"Fixing {failed_lib}") + shutil.move(failed_lib, failed_lib.replace("_failed.so", ".so")) + + if platform.system() == "Darwin": + openssl_libs = glob.glob(os.path.join(OPENSSL_OUTPUT_DIR, "lib", "lib*.dylib*")) + openssl_libs = [l for l in openssl_libs if os.path.islink(l) is False] + + python_openssl_libs = [] + + for lib_path in openssl_libs: + print(f"Copying {lib_path} to the python home") + dest = os.path.join(python_home, "lib", os.path.basename(lib_path)) + shutil.copyfile(lib_path, dest) + python_openssl_libs.append(dest) + + libs_to_patch = glob.glob( + os.path.join(python_home, "lib", "**", "*.so"), recursive=True + ) + + for lib_path in python_openssl_libs: + lib_name = os.path.basename(lib_path) + + for lib_to_patch in libs_to_patch: + print(f"Changing the library path of {lib_name} on {lib_to_patch}") + install_name_tool_change_args = [ + "install_name_tool", + "-change", + lib_path, + f"@rpath/{lib_name}", + lib_to_patch, + ] + + print(f"Executing {install_name_tool_change_args}") + subprocess.run(install_name_tool_change_args).check_returncode() + + elif platform.system() == "Linux": + openssl_libs = glob.glob(os.path.join(OPENSSL_OUTPUT_DIR, "lib", "lib*.so*")) + + for lib_path in openssl_libs: + print(f"Copying {lib_path} to the python home") + shutil.copy(lib_path, os.path.join(python_home, "lib")) + + elif platform.system() == "Windows": + openssl_dlls = glob.glob(os.path.join(OPENSSL_OUTPUT_DIR, "bin", "lib*")) + for dll_path in openssl_dlls: + print(f"Copying {dll_path} to the python home") + shutil.copy(dll_path, os.path.join(python_home, "bin")) + + openssl_libs = glob.glob(os.path.join(OPENSSL_OUTPUT_DIR, "lib", "lib*")) + for lib_path in openssl_libs: + print(f"Copying {lib_path} to the python home") + shutil.copy(lib_path, os.path.join(python_home, "libs")) + + python_interpreter_args = get_python_interpreter_args(python_home) + + # -I : isolate Python from the user's environment + python_interpreter_args.append("-I") + + ensure_pip_args = python_interpreter_args + ["-m", "ensurepip"] + print(f"Ensuring pip with {ensure_pip_args}") + subprocess.run(ensure_pip_args).check_returncode() + + pip_args = python_interpreter_args + ["-m", "pip"] + + for package in ["pip", "certifi", "six", "wheel", "packaging", "requests"]: + package_install_args = pip_args + [ + "install", + "--upgrade", + "--force-reinstall", + package, + ] + print(f"Installing {package} with {package_install_args}") + subprocess.run(package_install_args).check_returncode() + + wheel_install_args = pip_args + [ + "install", + "--upgrade", + "--force-reinstall", + "wheel", + ] + print(f"Installing wheel with {wheel_install_args}") + subprocess.run(wheel_install_args).check_returncode() + + site_packages = glob.glob( + os.path.join(python_home, "**", "site-packages"), recursive=True + )[0] + + if os.path.exists(site_packages) is False: + raise FileNotFoundError() + + print(f"Site packages found at {site_packages}") + + site_customize_path = os.path.join(site_packages, "sitecustomize.py") + if os.path.exists(site_customize_path): + os.remove(site_customize_path) + + print(f"Installing the sitecustomize.py payload at {site_customize_path}") + site_customize_path = os.path.join(site_packages, "sitecustomize.py") + if os.path.exists(site_customize_path): + print( + "Found a sitecustomize.py in the site-packages, the content of the file will be overwritten" + ) + os.remove(site_customize_path) + + with open(site_customize_path, "w") as sitecustomize_file: + sitecustomize_file.write(SITECUSTOMIZE_FILE_CONTENT) + + +def test_python_distribution(python_home: str) -> None: + """ + Test the Python distribution. + + :param python_home: Package root of an Python package + """ + tmp_dir = os.path.join(tempfile.gettempdir(), str(uuid.uuid4())) + os.makedirs(tmp_dir) + + tmp_python_home = os.path.join(tmp_dir, os.path.basename(python_home)) + try: + print(f"Moving {python_home} to {tmp_python_home}") + shutil.move(python_home, tmp_python_home) + + python_interpreter_args = get_python_interpreter_args(tmp_python_home) + + # Note: We need to build opentimelineio from sources in Windows+Debug + # because the official wheel links with the release version of + # python while RV uses the debug version. + build_opentimelineio = platform.system() == "Windows" and VARIANT == "Debug" + if build_opentimelineio: + print(f"Building opentimelineio") + + # Request an opentimelineio debug build + my_env = os.environ.copy() + my_env["OTIO_CXX_DEBUG_BUILD"] = "1" + + # Specify the location of the debug python import lib (eg. python39_d.lib) + python_lib_dir = os.path.join(tmp_python_home, "libs") + my_env["CMAKE_ARGS"] = f"-DCMAKE_LIBRARY_PATH={python_lib_dir}" + + opentimelineio_install_arg = python_interpreter_args + [ + "-m", + "pip", + "install", + ".", + ] + + result = subprocess.run( + opentimelineio_install_arg, + env=my_env, + cwd=OPENTIMELINEIO_SOURCE_DIR, + ).check_returncode() + + # Note : The OpenTimelineIO build will generate the pyd with names that are not loadable by default + # Example: _opentimed_d.cp39-win_amd64.pyd instead of _opentime_d.pyd + # and _otiod_d.cp39-win_amd64.pyd instead of _otio_d.pyd + # We fix those names here + otio_module_dir = os.path.join( + tmp_python_home, "lib", "site-packages", "opentimelineio" + ) + for _file in os.listdir(otio_module_dir): + if _file.endswith("pyd"): + otio_lib_name_split = os.path.basename(_file).split(".") + if len(otio_lib_name_split) > 2: + new_otio_lib_name = ( + otio_lib_name_split[0].replace("d_d", "_d") + ".pyd" + ) + src_file = os.path.join(otio_module_dir, _file) + dst_file = os.path.join(otio_module_dir, new_otio_lib_name) + shutil.copyfile(src_file, dst_file) + + wheel_install_arg = python_interpreter_args + [ + "-m", + "pip", + "install", + "cryptography", + ] + + if not build_opentimelineio: + wheel_install_arg.append("opentimelineio") + + print(f"Validating the that we can install a wheel with {wheel_install_arg}") + subprocess.run(wheel_install_arg).check_returncode() + + python_validation_args = python_interpreter_args + [ + "-c", + "\n".join( + [ + # Check for tkinter + "try:", + " import tkinter", + "except:", + " import Tkinter as tkinter", + # Make sure certifi is available + "import certifi", + # Make sure the SSL_CERT_FILE variable is sett + "import os", + "assert certifi.where() == os.environ['SSL_CERT_FILE']", + # Make sure ssl is correctly built and linked + "import ssl", + # Misc + "import sqlite3", + "import ctypes", + "import ssl", + "import _ssl", + "import zlib", + ] + ), + ] + print(f"Validating the python package with {python_validation_args}") + subprocess.run(python_validation_args).check_returncode() + + dummy_ssl_file = os.path.join("Path", "To", "Dummy", "File") + python_validation2_args = python_interpreter_args + [ + "-c", + "\n".join( + [ + "import os", + f"assert os.environ['SSL_CERT_FILE'] == '{dummy_ssl_file}'", + ] + ), + ] + print(f"Validating the python package with {python_validation2_args}") + subprocess.run( + python_validation2_args, env={**os.environ, "SSL_CERT_FILE": dummy_ssl_file} + ).check_returncode() + + python_validation3_args = python_interpreter_args + [ + "-c", + "\n".join( + [ + "import os", + "assert 'SSL_CERT_FILE' not in os.environ", + ] + ), + ] + print(f"Validating the python package with {python_validation3_args}") + subprocess.run( + python_validation3_args, + env={**os.environ, "DO_NOT_SET_SSL_CERT_FILE": "bleh"}, + ).check_returncode() + + finally: + print(f"Moving {tmp_python_home} to {python_home}") + shutil.move(tmp_python_home, python_home) + + +def clean() -> None: + """ + Run the clean step of the build. Removes everything. + """ + + if os.path.exists(OUTPUT_DIR): + shutil.rmtree(OUTPUT_DIR) + + if os.path.exists(TEMP_DIR): + shutil.rmtree(TEMP_DIR) + + git_clean_command = ["git", "clean", "-dxff"] + + print(f"Executing {git_clean_command} from {SOURCE_DIR}") + subprocess.run(git_clean_command, cwd=SOURCE_DIR) + + +def configure() -> None: + """ + Run the configure step of the build. It builds the makefile required to build Python with the path correctly set. + """ + + if platform.system() != "Windows": + configure_args = [ + os.path.join(SOURCE_DIR, "configure"), + f"--srcdir={SOURCE_DIR}", + f"--prefix={OUTPUT_DIR}", + f"--exec-prefix={OUTPUT_DIR}", + "--enable-shared", + f"--with-openssl={OPENSSL_OUTPUT_DIR}", + ] + + if VARIANT == "Release": + configure_args.append("--enable-optimizations") + + CPPFLAGS = [] + LD_LIBRARY_PATH = [] + + if ARCH: + configure_args = ["arch", ARCH] + configure_args + + if platform.system() == "Darwin": + configure_args.append("MACOSX_DEPLOYMENT_TARGET=10.14") + + readline_prefix_proc = subprocess.run( + ["brew", "--prefix", "readline"], capture_output=True + ) + readline_prefix_proc.check_returncode() + + tcl_prefix_proc = subprocess.run( + ["brew", "--prefix", "tcl-tk"], capture_output=True + ) + tcl_prefix_proc.check_returncode() + + xz_prefix_proc = subprocess.run( + ["brew", "--prefix", "xz"], capture_output=True + ) + xz_prefix_proc.check_returncode() + + sdk_prefix_proc = subprocess.run( + ["xcrun", "--show-sdk-path"], capture_output=True + ) + sdk_prefix_proc.check_returncode() + + readline_prefix = readline_prefix_proc.stdout.strip().decode("utf-8") + tcl_prefix = tcl_prefix_proc.stdout.strip().decode("utf-8") + xz_prefix = xz_prefix_proc.stdout.strip().decode("utf-8") + sdk_prefix = sdk_prefix_proc.stdout.strip().decode("utf-8") + + CPPFLAGS.append(f'-I{os.path.join(readline_prefix, "include")}') + CPPFLAGS.append(f'-I{os.path.join(tcl_prefix, "include")}') + CPPFLAGS.append(f'-I{os.path.join(xz_prefix, "include")}') + CPPFLAGS.append(f'-I{os.path.join(sdk_prefix, "usr", "include")}') + + LD_LIBRARY_PATH.append(os.path.join(readline_prefix, "lib")) + LD_LIBRARY_PATH.append(os.path.join(tcl_prefix, "lib")) + LD_LIBRARY_PATH.append(os.path.join(xz_prefix, "lib")) + LD_LIBRARY_PATH.append(os.path.join(sdk_prefix, "usr", "lib")) + + LDFLAGS = [f"-L{d}" for d in LD_LIBRARY_PATH] + + configure_args.append(f'CPPFLAGS={" ".join(CPPFLAGS)}') + configure_args.append(f'LDFLAGS={" ".join(LDFLAGS)}') + + if platform.system() == "Linux": + configure_args.append(f'LD_LIBRARY_PATH={":".join(LD_LIBRARY_PATH)}') + + print(f"Executing {configure_args} from {SOURCE_DIR}") + + subprocess.run( + configure_args, + cwd=SOURCE_DIR, + env={**os.environ, "LC_RPATH": os.path.join(OPENSSL_OUTPUT_DIR, "lib")}, + ).check_returncode() + + makefile_path = os.path.join(SOURCE_DIR, "Makefile") + old_makefile_path = os.path.join(SOURCE_DIR, "Makefile.old") + os.rename(makefile_path, old_makefile_path) + with open(old_makefile_path) as old_makefile: + with open(makefile_path, "w") as makefile: + for line in old_makefile: + new_line = line.replace( + "-Wl,-install_name,$(prefix)/lib/libpython$(", + "-Wl,-install_name,@rpath/libpython$(", + ) + + # Adjust RPaths + if new_line.startswith("LINKFORSHARED="): + if platform.system() == "Linux": + makefile.write( + new_line.strip() + + " -Wl,-rpath,'$$ORIGIN/../lib',-rpath,'$$ORIGIN/../Qt'" + + "\n" + ) + elif platform.system() == "Darwin": + makefile.write( + new_line.strip() + + " -Wl,-rpath,@executable_path/../lib,-rpath,@executable_path/../Qt" + + "\n" + ) + else: + makefile.write(new_line) + + +def build() -> None: + """ + Run the build step of the build. It compile every target of the project. + """ + + if platform.system() == "Windows": + + build_args = [ + os.path.join(SOURCE_DIR, "PCBuild", "build.bat"), + "-p", + "x64", + "-c", + VARIANT, + "-t", + "Rebuild", + ] + + print(f"Executing {build_args} from {SOURCE_DIR}") + + path_env = os.path.pathsep.join( + [ + os.path.dirname(sys.executable), + os.environ.get("PATH", ""), + ] + ) + + python_env = sys.executable + + subprocess.run( + build_args, + cwd=SOURCE_DIR, + env={ + **os.environ, + "LC_RPATH": os.path.join(OPENSSL_OUTPUT_DIR, "lib"), + "PYTHON": python_env, + "PATH": path_env, + }, + ).check_returncode() + else: + make_args = ["make", f"-j{os.cpu_count() or 1}"] + + if ARCH: + make_args = ["arch", ARCH] + make_args + + print(f"Executing {make_args} from {SOURCE_DIR}") + subprocess.run( + make_args, + cwd=SOURCE_DIR, + env={**os.environ, "LC_RPATH": os.path.join(OPENSSL_OUTPUT_DIR, "lib")}, + ).check_returncode() + + +def install() -> None: + """ + Run the install step of the build. It puts all the files inside of the output directory and make them ready to be + packaged. + """ + + if os.path.exists(OUTPUT_DIR): + shutil.rmtree(OUTPUT_DIR) + + if platform.system() == "Windows": + # include + src_dir = os.path.join(SOURCE_DIR, "Include") + dst_dir = os.path.join(OUTPUT_DIR, "include") + shutil.copytree(src_dir, dst_dir) + src_file = os.path.join(SOURCE_DIR, "PC", "pyconfig.h") + dst_file = os.path.join(dst_dir, "pyconfig.h") + shutil.copyfile(src_file, dst_file) + + # lib + src_dir = os.path.join(SOURCE_DIR, "Lib") + dst_dir = os.path.join(OUTPUT_DIR, "lib") + shutil.copytree(src_dir, dst_dir) + + # libs - required by pyside2 + dst_dir = os.path.join(OUTPUT_DIR, "libs") + os.mkdir(dst_dir) + python_libs = glob.glob( + os.path.join(SOURCE_DIR, "PCBuild", "amd64", "python*.lib") + ) + if python_libs: + for python_lib in python_libs: + shutil.copy(python_lib, dst_dir) + + # bin + src_dir = os.path.join(SOURCE_DIR, "PCBuild", "amd64") + dst_dir = os.path.join(OUTPUT_DIR, "bin") + shutil.copytree(src_dir, dst_dir) + # Create a python3.exe file to mimic Mac+Linux + if VARIANT == "Debug": + src_python_exe = "python_d.exe" + else: + src_python_exe = "python.exe" + src_file = os.path.join(src_dir, src_python_exe) + dst_file = os.path.join(dst_dir, "python3.exe") + shutil.copyfile(src_file, dst_file) + + else: + make_args = ["make", "install", f"-j{os.cpu_count() or 1}", "-s"] + + if ARCH: + make_args = ["arch", ARCH] + make_args + + print(f"Executing {make_args} from {SOURCE_DIR}") + + subprocess.run( + make_args, + cwd=SOURCE_DIR, + env={**os.environ, "LC_RPATH": os.path.join(OPENSSL_OUTPUT_DIR, "lib")}, + ).check_returncode() + + # Create the 'python' symlink + if platform.system() != "Windows": + python3_path = os.path.realpath(os.path.join(OUTPUT_DIR, "bin", "python3")) + python_path = os.path.join(os.path.dirname(python3_path), "python") + os.symlink(os.path.basename(python3_path), python_path) + + patch_python_distribution(OUTPUT_DIR) + test_python_distribution(OUTPUT_DIR) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + + parser.add_argument("--clean", dest="clean", action="store_true") + parser.add_argument("--configure", dest="configure", action="store_true") + parser.add_argument("--build", dest="build", action="store_true") + parser.add_argument("--install", dest="install", action="store_true") + + parser.add_argument("--source-dir", dest="source", type=pathlib.Path, required=True) + parser.add_argument( + "--openssl-dir", dest="openssl", type=pathlib.Path, required=True + ) + parser.add_argument("--temp-dir", dest="temp", type=pathlib.Path, required=True) + parser.add_argument("--output-dir", dest="output", type=pathlib.Path, required=True) + + parser.add_argument("--variant", dest="variant", type=str, required=True) + parser.add_argument("--arch", dest="arch", type=str, required=False, default="") + + parser.add_argument( + "--opentimelineio-source-dir", + dest="otio_source_dir", + type=str, + required=False, + default="", + ) + + parser.set_defaults(clean=False, configure=False, build=False, install=False) + + args = parser.parse_args() + + SOURCE_DIR = args.source + OUTPUT_DIR = args.output + TEMP_DIR = args.temp + OPENSSL_OUTPUT_DIR = args.openssl + VARIANT = args.variant + ARCH = args.arch + OPENTIMELINEIO_SOURCE_DIR = args.otio_source_dir + + if args.clean: + clean() + + if args.configure: + configure() + + if args.build: + build() + + if args.install: + install() diff --git a/src/build/meson_arch_x86_64.txt b/src/build/meson_arch_x86_64.txt new file mode 100644 index 000000000..e4ebf923f --- /dev/null +++ b/src/build/meson_arch_x86_64.txt @@ -0,0 +1,13 @@ +[host_machine] +system = 'darwin' +cpu_family = 'x86_64' +cpu = 'x86_64' +endian = 'little' + +[binaries] +c = 'clang' +objc = 'clang' +cpp = 'clang++' + +[built-in options] +c_args = ['-arch', 'x86_64'] diff --git a/src/build/patch_PySide2/windows_desktop.py b/src/build/patch_PySide2/windows_desktop.py new file mode 100644 index 000000000..344a71625 --- /dev/null +++ b/src/build/patch_PySide2/windows_desktop.py @@ -0,0 +1,523 @@ +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import functools +import os +import sys +import fnmatch + +from ..config import config +from ..options import OPTION +from ..utils import copydir, copyfile, makefile +from ..utils import regenerate_qt_resources, filter_match +from ..utils import download_and_extract_7z + + +def prepare_packages_win32(self, vars): + # For now, debug symbols will not be shipped into the package. + copy_pdbs = False + pdbs = [] + if ( + self.debug or OPTION["DEBUG"] or self.build_type == "RelWithDebInfo" + ) and copy_pdbs: + pdbs = ["*.pdb"] + + # /lib/site-packages/{st_package_name}/* -> + # /{st_package_name} + # This copies the module .pyd files and various .py files + # (__init__, config, git version, etc.) + copydir( + "{site_packages_dir}/{st_package_name}", + "{st_build_dir}/{st_package_name}", + vars=vars, + ) + + if config.is_internal_shiboken_module_build(): + # /shiboken2/doc/html/* -> + # /{st_package_name}/docs/shiboken2 + copydir( + "{build_dir}/shiboken2/doc/html", + "{st_build_dir}/{st_package_name}/docs/shiboken2", + force=False, + vars=vars, + ) + + # /bin/*.dll -> {st_package_name}/ + copydir( + "{install_dir}/bin/", + "{st_build_dir}/{st_package_name}", + filter=["shiboken*.dll"], + recursive=False, + vars=vars, + ) + + # /lib/*.lib -> {st_package_name}/ + copydir( + "{install_dir}/lib/", + "{st_build_dir}/{st_package_name}", + filter=["shiboken*.lib"], + recursive=False, + vars=vars, + ) + + # @TODO: Fix this .pdb file not to overwrite release + # {shibokengenerator}.pdb file. + # Task-number: PYSIDE-615 + copydir( + "{build_dir}/shiboken2/shibokenmodule", + "{st_build_dir}/{st_package_name}", + filter=pdbs, + recursive=False, + vars=vars, + ) + + # pdb files for libshiboken and libpyside + copydir( + "{build_dir}/shiboken2/libshiboken", + "{st_build_dir}/{st_package_name}", + filter=pdbs, + recursive=False, + vars=vars, + ) + + if config.is_internal_shiboken_generator_build(): + # /bin/*.dll -> {st_package_name}/ + copydir( + "{install_dir}/bin/", + "{st_build_dir}/{st_package_name}", + filter=["shiboken*.exe"], + recursive=False, + vars=vars, + ) + + # Used to create scripts directory. + makefile("{st_build_dir}/{st_package_name}/scripts/shiboken_tool.py", vars=vars) + + # For setting up setuptools entry points. + copyfile( + "{install_dir}/bin/shiboken_tool.py", + "{st_build_dir}/{st_package_name}/scripts/shiboken_tool.py", + force=False, + vars=vars, + ) + + # @TODO: Fix this .pdb file not to overwrite release + # {shibokenmodule}.pdb file. + # Task-number: PYSIDE-615 + copydir( + "{build_dir}/shiboken2/generator", + "{st_build_dir}/{st_package_name}", + filter=pdbs, + recursive=False, + vars=vars, + ) + + if ( + config.is_internal_shiboken_generator_build() + or config.is_internal_pyside_build() + ): + # /include/* -> /{st_package_name}/include + copydir( + "{install_dir}/include/{cmake_package_name}", + "{st_build_dir}/{st_package_name}/include", + vars=vars, + ) + + if config.is_internal_pyside_build(): + # /pyside2/{st_package_name}/*.pdb -> + # /{st_package_name} + copydir( + "{build_dir}/pyside2/{st_package_name}", + "{st_build_dir}/{st_package_name}", + filter=pdbs, + recursive=False, + vars=vars, + ) + + makefile("{st_build_dir}/{st_package_name}/scripts/__init__.py", vars=vars) + + # For setting up setuptools entry points + copyfile( + "{install_dir}/bin/pyside_tool.py", + "{st_build_dir}/{st_package_name}/scripts/pyside_tool.py", + force=False, + vars=vars, + ) + + # /bin/*.exe,*.dll -> {st_package_name}/ + copydir( + "{install_dir}/bin/", + "{st_build_dir}/{st_package_name}", + filter=["pyside*.exe", "pyside*.dll", "uic.exe", "rcc.exe", "designer.exe"], + recursive=False, + vars=vars, + ) + + # /lib/*.lib -> {st_package_name}/ + copydir( + "{install_dir}/lib/", + "{st_build_dir}/{st_package_name}", + filter=["pyside*.lib"], + recursive=False, + vars=vars, + ) + + # /share/{st_package_name}/typesystems/* -> + # /{st_package_name}/typesystems + copydir( + "{install_dir}/share/{st_package_name}/typesystems", + "{st_build_dir}/{st_package_name}/typesystems", + vars=vars, + ) + + # /share/{st_package_name}/glue/* -> + # /{st_package_name}/glue + copydir( + "{install_dir}/share/{st_package_name}/glue", + "{st_build_dir}/{st_package_name}/glue", + vars=vars, + ) + + # /pyside2/{st_package_name}/support/* -> + # /{st_package_name}/support/* + copydir( + "{build_dir}/pyside2/{st_package_name}/support", + "{st_build_dir}/{st_package_name}/support", + vars=vars, + ) + + # /pyside2/{st_package_name}/*.pyi -> + # /{st_package_name}/*.pyi + copydir( + "{build_dir}/pyside2/{st_package_name}", + "{st_build_dir}/{st_package_name}", + filter=["*.pyi", "py.typed"], + vars=vars, + ) + + copydir( + "{build_dir}/pyside2/libpyside", + "{st_build_dir}/{st_package_name}", + filter=pdbs, + recursive=False, + vars=vars, + ) + + if not OPTION["NOEXAMPLES"]: + + def pycache_dir_filter(dir_name, parent_full_path, dir_full_path): + if fnmatch.fnmatch(dir_name, "__pycache__"): + return False + return True + + # examples/* -> /{st_package_name}/examples + copydir( + os.path.join(self.script_dir, "examples"), + "{st_build_dir}/{st_package_name}/examples", + force=False, + vars=vars, + dir_filter_function=pycache_dir_filter, + ) + # Re-generate examples Qt resource files for Python 3 + # compatibility + if sys.version_info[0] == 3: + examples_path = "{st_build_dir}/{st_package_name}/examples".format( + **vars + ) + pyside_rcc_path = "{install_dir}/bin/rcc.exe".format(**vars) + pyside_rcc_options = ["-g", "python"] + regenerate_qt_resources( + examples_path, pyside_rcc_path, pyside_rcc_options + ) + + if vars["ssl_libs_dir"]: + # /* -> /{st_package_name}/openssl + copydir( + "{ssl_libs_dir}", + "{st_build_dir}/{st_package_name}/openssl", + filter=["libeay32.dll", "ssleay32.dll"], + force=False, + vars=vars, + ) + + if config.is_internal_shiboken_module_build(): + # The C++ std library dlls need to be packaged with the + # shiboken module, because libshiboken uses C++ code. + copy_msvc_redist_files(vars, "{build_dir}/msvc_redist".format(**vars)) + + if ( + config.is_internal_pyside_build() + or config.is_internal_shiboken_generator_build() + ): + copy_qt_artifacts(self, copy_pdbs, vars) + copy_msvc_redist_files(vars, "{build_dir}/msvc_redist".format(**vars)) + + +def copy_msvc_redist_files(vars, redist_target_path): + # MSVC redistributable file list. + msvc_redist = [ + "concrt140.dll", + "msvcp140.dll", + "ucrtbase.dll", + "vcamp140.dll", + "vccorlib140.dll", + "vcomp140.dll", + "vcruntime140.dll", + "vcruntime140_1.dll", + "msvcp140_1.dll", + "msvcp140_2.dll", + "msvcp140_codecvt_ids.dll", + ] + + # Make a directory where the files should be extracted. + if not os.path.exists(redist_target_path): + os.makedirs(redist_target_path) + + # Extract Qt dependency dlls when building on Qt CI. + in_coin = os.environ.get("COIN_LAUNCH_PARAMETERS", None) + if in_coin is not None: + redist_url = "http://download.qt.io/development_releases/prebuilt/vcredist/" + zip_file = "pyside_qt_deps_64_2019.7z" + if "{target_arch}".format(**vars) == "32": + zip_file = "pyside_qt_deps_32_2019.7z" + download_and_extract_7z(redist_url + zip_file, redist_target_path) + else: + print("Qt dependency DLLs (MSVC redist) will not be downloaded and extracted.") + + copydir( + redist_target_path, + "{st_build_dir}/{st_package_name}", + filter=msvc_redist, + recursive=False, + vars=vars, + ) + + +def copy_qt_artifacts(self, copy_pdbs, vars): + built_modules = self.get_built_pyside_config(vars)["built_modules"] + + constrain_modules = None + copy_plugins = True + copy_qml = True + copy_translations = True + copy_qt_conf = True + copy_qt_permanent_artifacts = True + copy_msvc_redist = False + copy_clang = False + + if config.is_internal_shiboken_generator_build(): + constrain_modules = ["Core", "Network", "Xml", "XmlPatterns"] + copy_plugins = False + copy_qml = False + copy_translations = False + copy_qt_conf = False + copy_qt_permanent_artifacts = False + copy_msvc_redist = True + copy_clang = True + + # /bin/*.dll and Qt *.exe -> /{st_package_name} + qt_artifacts_permanent = [ + "opengl*.dll", + "d3d*.dll", + "designer.exe", + "linguist.exe", + "lrelease.exe", + "lupdate.exe", + "lconvert.exe", + "qtdiag.exe", + ] + + # Choose which EGL library variants to copy. + qt_artifacts_egl = ["libEGL{}.dll", "libGLESv2{}.dll"] + if self.qtinfo.build_type != "debug_and_release": + egl_suffix = "*" + elif self.debug or OPTION["DEBUG"]: + egl_suffix = "d" + else: + egl_suffix = "" + qt_artifacts_egl = [a.format(egl_suffix) for a in qt_artifacts_egl] + + artifacts = [] + if copy_qt_permanent_artifacts: + artifacts += qt_artifacts_permanent + artifacts += qt_artifacts_egl + + if copy_msvc_redist: + # The target path has to be qt_bin_dir at the moment, + # because the extracted archive also contains the opengl32sw + # and the d3dcompiler dlls, which are copied not by this + # function, but by the copydir below. + copy_msvc_redist_files(vars, "{qt_bin_dir}".format(**vars)) + + if artifacts: + copydir( + "{qt_bin_dir}", + "{st_build_dir}/{st_package_name}", + filter=artifacts, + recursive=False, + vars=vars, + ) + + # /bin/*.dll and Qt *.pdbs -> /{st_package_name} part two + # File filter to copy only debug or only release files. + if constrain_modules: + qt_dll_patterns = ["Qt5" + x + "{}.dll" for x in constrain_modules] + if copy_pdbs: + qt_dll_patterns += ["Qt5" + x + "{}.pdb" for x in constrain_modules] + else: + qt_dll_patterns = ["Qt5*{}.dll", "lib*{}.dll"] + if copy_pdbs: + qt_dll_patterns += ["Qt5*{}.pdb", "lib*{}.pdb"] + + def qt_build_config_filter(patterns, file_name, file_full_path): + release = [a.format("") for a in patterns] + debug = [a.format("d") for a in patterns] + + # If qt is not a debug_and_release build, that means there + # is only one set of shared libraries, so we can just copy + # them. + if self.qtinfo.build_type != "debug_and_release": + if filter_match(file_name, release): + return True + return False + + # In debug_and_release case, choosing which files to copy + # is more difficult. We want to copy only the files that + # match the PySide2 build type. So if PySide2 is built in + # debug mode, we want to copy only Qt debug libraries + # (ending with "d.dll"). Or vice versa. The problem is that + # some libraries have "d" as the last character of the + # actual library name (for example Qt5Gamepad.dll and + # Qt5Gamepadd.dll). So we can't just match a pattern ending + # in "d". Instead we check if there exists a file with the + # same name plus an additional "d" at the end, and using + # that information we can judge if the currently processed + # file is a debug or release file. + + # e.g. ["Qt5Cored", ".dll"] + file_split = os.path.splitext(file_name) + file_base_name = file_split[0] + file_ext = file_split[1] + # e.g. "/home/work/qt/qtbase/bin" + file_path_dir_name = os.path.dirname(file_full_path) + # e.g. "Qt5Coredd" + maybe_debug_name = "{}d".format(file_base_name) + if self.debug or OPTION["DEBUG"]: + filter = debug + + def predicate(path): + return not os.path.exists(path) + + else: + filter = release + + def predicate(path): + return os.path.exists(path) + + # e.g. "/home/work/qt/qtbase/bin/Qt5Coredd.dll" + other_config_path = os.path.join( + file_path_dir_name, maybe_debug_name + file_ext + ) + + if filter_match(file_name, filter) and predicate(other_config_path): + return True + return False + + qt_dll_filter = functools.partial(qt_build_config_filter, qt_dll_patterns) + copydir( + "{qt_bin_dir}", + "{st_build_dir}/{st_package_name}", + file_filter_function=qt_dll_filter, + recursive=False, + vars=vars, + ) + + if copy_plugins: + # /plugins/* -> /{st_package_name}/plugins + plugin_dll_patterns = ["*{}.dll"] + pdb_pattern = "*{}.pdb" + if copy_pdbs: + plugin_dll_patterns += [pdb_pattern] + plugin_dll_filter = functools.partial( + qt_build_config_filter, plugin_dll_patterns + ) + copydir( + "{qt_plugins_dir}", + "{st_build_dir}/{st_package_name}/plugins", + file_filter_function=plugin_dll_filter, + vars=vars, + ) + + if copy_translations: + # /translations/* -> /{st_package_name}/translations + copydir( + "{qt_translations_dir}", + "{st_build_dir}/{st_package_name}/translations", + filter=["*.qm", "*.pak"], + force=False, + vars=vars, + ) + + if copy_qml: + # /qml/* -> /{st_package_name}/qml + qml_dll_patterns = ["*{}.dll"] + qml_ignore_patterns = qml_dll_patterns + [pdb_pattern] + qml_ignore = [a.format("") for a in qml_ignore_patterns] + + # Copy all files that are not dlls and pdbs (.qml, qmldir). + copydir( + "{qt_qml_dir}", + "{st_build_dir}/{st_package_name}/qml", + ignore=qml_ignore, + force=False, + recursive=True, + vars=vars, + ) + + if copy_pdbs: + qml_dll_patterns += [pdb_pattern] + qml_dll_filter = functools.partial(qt_build_config_filter, qml_dll_patterns) + + # Copy all dlls (and possibly pdbs). + copydir( + "{qt_qml_dir}", + "{st_build_dir}/{st_package_name}/qml", + file_filter_function=qml_dll_filter, + force=False, + recursive=True, + vars=vars, + ) + + if self.is_webengine_built(built_modules): + copydir( + "{qt_prefix_dir}/resources", + "{st_build_dir}/{st_package_name}/resources", + filter=None, + recursive=False, + vars=vars, + ) + + filter = "QtWebEngineProcess{}.exe".format( + "d" if (self.debug or OPTION["DEBUG"]) else "" + ) + copydir( + "{qt_bin_dir}", + "{st_build_dir}/{st_package_name}", + filter=[filter], + recursive=False, + vars=vars, + ) + + if copy_qt_conf: + # Copy the qt.conf file to prefix dir. + copyfile( + "{build_dir}/pyside2/{st_package_name}/qt.conf", + "{st_build_dir}/{st_package_name}", + vars=vars, + ) + + if copy_clang: + self.prepare_standalone_clang(is_win=True) diff --git a/src/build/quoteFile.py b/src/build/quoteFile.py new file mode 100755 index 000000000..6b1648728 --- /dev/null +++ b/src/build/quoteFile.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2001, Tweak Films +# Copyright (c) 2008, Tweak Software +# +# SPDX-License-Identifier: Apache-2.0 +# +# Quote a text file in C++ form for inclusion into a C++ source file +# +import sys +import string + +index = 1 +varlist = [] + +while sys.argv[index] == "-s": + assign = sys.argv[index + 1] + parts = str.split(assign, "=") + varlist.append(parts) + index += 2 + +infilename = sys.argv[index] +outfilename = sys.argv[index + 1] +cppname = sys.argv[index + 2] + +infile = open(infilename, "r") +outfile = open(outfilename, "w") +lines = infile.readlines() + +outfile.write("//\n") +outfile.write("// **** DO NOT EDIT ****\n") +outfile.write("// This file -- " + outfilename + " -- was automatically generated\n") +outfile.write("// from the file " + infilename + " by quoteFile.\n") +outfile.write("//\n") + +outfile.write("const char* " + cppname + " = ") + +for line in lines: + l = line.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") + for subst in varlist: + l = l.replace(subst[0], subst[1]) + outfile.write('"' + l + '"\n') + +outfile.write(";\n") +outfile.close() + +# Local Variables: +# mode: python +# end: diff --git a/src/build/remove_absolute_rpath.py b/src/build/remove_absolute_rpath.py new file mode 100755 index 000000000..448d8c8a0 --- /dev/null +++ b/src/build/remove_absolute_rpath.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python + +# ***************************************************************************** +# Copyright (c) 2020 Autodesk, Inc. +# All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# ***************************************************************************** +import argparse +import subprocess +import os + +from pathlib import Path + + +def get_object_files(target): + target = Path(target) + + if target.is_dir(): + for root, dirs, files in os.walk(target): + for f in files: + path = Path(root) / f + + file_type = subprocess.check_output(["file", "-bh", path]) + if file_type.startswith(b"Mach-O"): + yield path + else: + yield target + + +def get_rpaths(object_file_path): + otool_output = subprocess.check_output( + ["otool", "-l", object_file_path] + ).splitlines() + + i = 0 + while i < len(otool_output): + otool_line = otool_output[i].strip() + i += 1 + + if otool_line.split()[-1] == b"LC_RPATH": + for y in range(i, i + 2): + rpath_line = otool_output[y].split() + + if rpath_line[0] == b"path": + yield rpath_line[1] + break + + +def delete_rpath(object_file_path, rpath): + delete_rpath_command = [ + "install_name_tool", + "-delete_rpath", + rpath, + object_file_path, + ] + subprocess.run(delete_rpath_command).check_returncode() + + +def remove_absolute_rpath(target): + for file in get_object_files(target): + print(f"Found Mach-O file: {file}") + + for rpath in get_rpaths(file): + output = f"\trpath: {rpath}" + + if rpath.startswith(b"@") is False and rpath.startswith(b".") is False: + delete_rpath(file, rpath) + output += " (Deleted)" + + print(output) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + + parser.add_argument("--target", type=Path, help="Target to sanitize", required=True) + + args = parser.parse_args() + + if args.target.exists() is False: + raise FileNotFoundError(f"Unable to locate {args.target.absolute()}") + + remove_absolute_rpath(args.target) diff --git a/src/build/requirements.txt b/src/build/requirements.txt new file mode 100644 index 000000000..f3ad9ef23 --- /dev/null +++ b/src/build/requirements.txt @@ -0,0 +1,19 @@ +# This file contains all the packages that will be packaged with RV. Please add the license next to the package. + +pip # License: MIT License (MIT) +setuptools # License: MIT License +OpenTimelineIO # License: Other/Proprietary License (Modified Apache 2.0 License) +matplotlib # License: Python Software Foundation License (PSF) +networkx # License: BSD License +PyOpenGL # License: BSD License (BSD) +PyOpenGL_accelerate # License: BSD License (BSD) + + +# Those are installed by the src/build/make_python.py script, adding them here to list their licenses. + +certifi # License: Mozilla Public License 2.0 (MPL 2.0) (MPL-2.0) +six # License: MIT License (MIT) +wheel # License: MIT License (MIT) +packaging # License: Apache Software License, BSD License +requests # License: Apache Software License (Apache 2.0) +cryptography # License: Apache Software License, BSD License ((Apache-2.0 OR BSD-3-Clause) AND PSF-2.0) diff --git a/src/build/utils.py b/src/build/utils.py new file mode 100644 index 000000000..c9208b4ba --- /dev/null +++ b/src/build/utils.py @@ -0,0 +1,435 @@ +# -*- coding: utf-8 -*- + +# ***************************************************************************** +# Copyright 2020 Autodesk, Inc. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +# ***************************************************************************** + +import itertools +import os +import platform +import requests +import shutil +import subprocess +import sys + +from py7zr import SevenZipFile, exceptions +from typing import List +from zipfile import ZipFile + + +def _windows_zip_impl(input_folder: str, output_zip: str) -> None: + """ + Windows implementation of the zip function. + + This implementation relies on shutil.make_archive because nothing special have to be done on Windows. + + :param str input_folder: Folder to archive + :param str output_zip: Archive file name + """ + result = shutil.make_archive(output_zip, "zip", input_folder) + os.rename(result, output_zip) # make_archive append .zip and this is not desirable. + + +def _windows_unzip_impl(input_zip: str, output_folder: str) -> None: + """ + Windows implementation of the zip function. + + This implementation relies on ZipFile because nothing special have to be done on Windows. + + :param input_zip: Path to the zip file to uncompress + :param output_folder: Location to wich the zip have to be uncompressed + """ + with ZipFile(input_zip) as zip_file: + zip_file.extractall(output_folder) + + +def _darwin_zip_impl(input_folder: str, output_zip: str) -> None: + """ + macOS implementation of the zip function. + + This implementation relies on ditto so all the MacOS special sauce can be captured inside the archive. + + :param str input_folder: Folder to archive + :param str output_zip: Archive file name + """ + zip_args = ["/usr/bin/ditto", "-c", "-k", input_folder, output_zip] + + print(f"Executing {zip_args}") + subprocess.run(zip_args).check_returncode() + + +def _darwin_unzip_impl(input_zip: str, output_folder: str) -> None: + """ + macOS implementation of the zip function. + + This implementation relies on ditto so all the MacOS special sauce can be extracted from the archive. + + :param input_zip: Path to the zip file to uncompress + :param output_folder: Location to which the zip have to be uncompressed + """ + unzip_args = ["/usr/bin/ditto", "-x", "-k", input_zip, output_folder] + + print(f"Executing {unzip_args}") + subprocess.run(unzip_args).check_returncode() + + +def _linux_zip_impl(input_folder: str, output_zip: str) -> None: + """ + Linux implementation of the zip function. + + This implementation relies on the zip command line tool. This is prefered because that way, we can archive keeping + the symlinks as-is. + + :param str input_folder: Folder to archive + :param str output_zip: Archive file name + """ + zip_args = ["zip", "--symlinks", "-r", output_zip, "."] + + print(f"Executing {zip_args}") + subprocess.run(zip_args, cwd=input_folder).check_returncode() + + +def _linux_unzip_impl(input_zip: str, output_folder: str) -> None: + """ + Linux implementation of the zip function. + + This implementation relies on the unzip command line tool. This is prefered because ZipFile doesn't preserve + symlinks. + + :param input_zip: Path to the zip file to uncompress + :param output_folder: Location to which the zip have to be uncompressed + """ + unzip_args = ["unzip", "-q", input_zip, "-d", output_folder] + + print(f"Executing {unzip_args}") + subprocess.run(unzip_args).check_returncode() + + +def create_zip_archive(input_folder: str, output_zip: str) -> None: + """ + Implementation of the zip function. + + :param str input_folder: Folder to archive + :param str output_zip: Archive file name + """ + print(f"Archiving {input_folder} into {output_zip}") + + if platform.system() == "Linux": + _linux_zip_impl(input_folder, output_zip) + elif platform.system() == "Darwin": + _darwin_zip_impl(input_folder, output_zip) + else: + _windows_zip_impl(input_folder, output_zip) + + +def extract_zip_archive(input_zip: str, output_folder: str) -> None: + """ + Implementation of the unzip function. + + :param input_zip: Path to the zip file to uncompress + :param output_folder: Location to which the zip have to be uncompressed + """ + print(f"Extracting {input_zip} into {output_folder}") + + if platform.system() == "Linux": + _linux_unzip_impl(input_zip, output_folder) + elif platform.system() == "Darwin": + _darwin_unzip_impl(input_zip, output_folder) + else: + _windows_unzip_impl(input_zip, output_folder) + + +def extract_7z_archive(input_7z: str, output_folder: str) -> None: + """ + Implementation of the uncompress function for 7z files. + + :param input_zip: Path to the 7z file to uncompress + :param output_folder: Location to which the 7z have to be uncompressed + """ + print(f"Extracting {input_7z} into {output_folder}") + + with SevenZipFile(input_7z, "r") as z: + z.extractall(output_folder) + + +def verify_7z_archive(input_7z: str) -> bool or None: + """ + Returns True/False if CRC-check passes or None if there is no CRC + + :param input_zip: Path to the 7z file to uncompress + """ + print(f"Verifying {input_7z}") + try: + with SevenZipFile(input_7z, "r") as z: + return z.test() + except exceptions.Bad7zFile: + return None + + +def _darwin_copy_recursively(src_folder: str, destination_folder) -> None: + """ + Implementation of the copy function. + + This implementation uses ditto as it's the preferred copy command on MacOS X + + :param src_folder: Path of source of the copy + :param destination_folder: Path to the destination the copy + """ + copy_args = ["ditto", src_folder, destination_folder] + + print(f"Executing {copy_args}") + subprocess.run(copy_args).check_returncode() + + +def _linux_copy_recursively(src_folder: str, destination_folder) -> None: + """ + Implementation of the copy function. + + This implementation uses cp as it's the preferred copy command on Linux. + + :param src_folder: Path of source of the copy + :param destination_folder: Path to the destination the copy + """ + copy_args = ["cp", "-r", src_folder, destination_folder] + + print(f"Executing {copy_args}") + subprocess.run(copy_args).check_returncode() + + +def _windows_copy_recursively(src_folder: str, destination_folder) -> None: + """ + Implementation of the copy function. + + :param src_folder: Path of source of the copy + :param destination_folder: Path to the destination the copy + """ + raise NotImplementedError() + + +def copy_recursively(src_folder: str, destination_folder: str) -> None: + """ + Implementation of the copy function. + + :param src_folder: Path of source of the copy + :param destination_folder: Path to the destination the copy + """ + + print(f"Copying {src_folder} into {destination_folder}") + + if platform.system() == "Linux": + _linux_copy_recursively(src_folder, destination_folder) + elif platform.system() == "Darwin": + _darwin_copy_recursively(src_folder, destination_folder) + else: + _windows_copy_recursively(src_folder, destination_folder) + + +def download_file(url, file_path): + """ + Download a file with HTTP or HTTPS. + + param: url: url to download + param file_path: destination file path + """ + + print(f"Downloading {url} into {file_path}") + req = requests.get(url, stream=True) + + with open(file_path, "wb") as file: + total = req.headers.get("content-length") + if total is None: + file.write(req.content) + else: + total = int(total) + current = 0 + + for chunk in req.iter_content(round(total / 100)): + current += len(chunk) + file.write(chunk) + print(f"{current} / {total} ({round((current / total) * 100)}%)") + + +def get_cygpath_windows(cygpath: str) -> str: + """ + Returns the windows path corresponding to the cygpath passed as parameter. + :param string cygpath: cygpath to be translated. Example: C:/git/OpenRV/_build + """ + return ( + subprocess.check_output(["cygpath", "--windows", "--absolute", f"{cygpath}"]) + .rstrip() + .decode("utf-8") + ) + + +def update_env_path(newpaths: str) -> None: + """ + Augment the PATH environment variable with newpaths + """ + paths = os.environ["PATH"].lower().split(os.pathsep) + for path in newpaths: + if not path.lower() in paths: + paths.insert(0, path) + os.environ["PATH"] = "{}{}{}".format(path, os.pathsep, os.environ["PATH"]) + + +def source_widows_msvc_env(msvc_year: str) -> None: + """ + Source the Microsoft Windows Visual Studio x environment so that a subprocess build + inherits the proper msvc environement to build. + Note: Equivalent to call vcvars64.bat from a command prompt. + :param string msvc_year: Visual Studio x environment to be sourced. Example: 2019 + """ + + def get_environment_from_batch_command(env_cmd, initial=None): + """ + Take a command (either a single command or list of arguments) + and return the environment created after running that command. + Note that the command must be a batch file or .cmd file, or the + changes to the environment will not be captured. + + If initial is supplied, it is used as the initial environment passed + to the child process. + """ + + def validate_pair(ob): + try: + if not (len(ob) == 2): + print("Unexpected result: {}".format(ob)) + raise ValueError + except: + return False + return True + + def consume(iter): + try: + while True: + next(iter) + except StopIteration: + pass + + if not isinstance(env_cmd, (list, tuple)): + env_cmd = [env_cmd] + # construct the command that will alter the environment + env_cmd = subprocess.list2cmdline(env_cmd) + # create a tag so we can tell in the output when the proc is done + tag = "Done running command" + # construct a cmd.exe command to do accomplish this + cmd = 'cmd.exe /E:ON /V:ON /s /c "{} && echo "{}" && set"'.format(env_cmd, tag) + # launch the process + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=initial) + # parse the output sent to stdout + lines = proc.stdout + if sys.version_info[0] > 2: + # make sure the lines are strings + lines = map(lambda s: s.decode(), lines) + # consume whatever output occurs until the tag is reached + consume(itertools.takewhile(lambda l: tag not in l, lines)) + # define a way to handle each KEY=VALUE line + # parse key/values into pairs + pairs = map(lambda l: l.rstrip().split("=", 1), lines) + # make sure the pairs are valid + valid_pairs = filter(validate_pair, pairs) + # construct a dictionary of the pairs + result = dict(valid_pairs) + # let the process finish + proc.communicate() + return result + + mvs_base_path = os.path.join( + os.path.expandvars("%SystemDrive%") + os.sep, + "Program Files (x86)", + "Microsoft Visual Studio", + ) + vcvars_path = os.path.join( + mvs_base_path, + msvc_year, + "Professional", + "VC", + "Auxiliary", + "Build", + "vcvars64.bat", + ) + if not os.path.exists(vcvars_path): + vcvars_path = os.path.join( + mvs_base_path, + msvc_year, + "Entreprise", + "VC", + "Auxiliary", + "Build", + "vcvars64.bat", + ) + if not os.path.exists(vcvars_path): + vcvars_path = os.path.join( + mvs_base_path, + msvc_year, + "Community", + "VC", + "Auxiliary", + "Build", + "vcvars64.bat", + ) + + if not os.path.exists(vcvars_path): + print( + "ERROR: Failed to find the MSVC compiler environment init script (vcvars64.bat) on your system." + ) + return + else: + print( + f"Found MSVC compiler environment init script (vcvars64.bat):\n{vcvars_path}" + ) + + # Get MSVC env + vcvars_cmd = [vcvars_path] + msvc_env = get_environment_from_batch_command(vcvars_cmd) + msvc_env_paths = os.pathsep.join( + [msvc_env[k] for k in msvc_env if k.upper() == "PATH"] + ).split(os.pathsep) + msvc_env_without_paths = dict( + [(k, msvc_env[k]) for k in msvc_env if k.upper() != "PATH"] + ) + + # Extend os.environ with MSVC env + update_env_path(msvc_env_paths) + for k in sorted(msvc_env_without_paths): + v = msvc_env_without_paths[k] + os.environ[k] = v + + +def get_mingw64_path_on_windows(winpath: str) -> str: + """ + On Windows: returns the mingw64 path corresponding to the windows passed as parameter. + On other platforms: simply returns the path passed as parameter + :param string winpath: winpath to be translated. Example: C:\git\OpenRV\_build + """ + if platform.system() != "Windows": + return winpath + + return ( + subprocess.check_output( + ["c:\\msys64\\usr\\bin\\cygpath.exe", "--mixed", "--absolute", f"{winpath}"] + ) + .rstrip() + .decode("utf-8") + ) + + +def get_mingw64_args(cmd_args: List[str]) -> List[str]: + + updated_cmd_args = cmd_args + updated_cmd_args.append(";sleep 10") + + mingw64_cmd_args = [ + "c:\\msys64\\msys2_shell.cmd", + "-defterm", + "-mingw64", + "-here", + "-c", + " ".join(updated_cmd_args), + ] + + return mingw64_cmd_args diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt new file mode 100644 index 000000000..9a7aa5eb9 --- /dev/null +++ b/src/lib/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +ADD_SUBDIRECTORY(base) +ADD_SUBDIRECTORY(mu) +ADD_SUBDIRECTORY(app) +ADD_SUBDIRECTORY(audio) +ADD_SUBDIRECTORY(image) +ADD_SUBDIRECTORY(geometry) +ADD_SUBDIRECTORY(ip) +ADD_SUBDIRECTORY(files) +ADD_SUBDIRECTORY(ui) +ADD_SUBDIRECTORY(qt) +ADD_SUBDIRECTORY(graphics) +ADD_SUBDIRECTORY(network) +ADD_SUBDIRECTORY(oiio) diff --git a/src/lib/app/CMakeLists.txt b/src/lib/app/CMakeLists.txt new file mode 100644 index 000000000..5d7a14c0d --- /dev/null +++ b/src/lib/app/CMakeLists.txt @@ -0,0 +1,21 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +IF(RV_TARGET_DARWIN) + ADD_SUBDIRECTORY(DarwinBundle) +ENDIF() +ADD_SUBDIRECTORY(mu_rvui) +ADD_SUBDIRECTORY(py_rvui) +ADD_SUBDIRECTORY(RvApp) +ADD_SUBDIRECTORY(MuTwkApp) +ADD_SUBDIRECTORY(PyTwkApp) +ADD_SUBDIRECTORY(TwkMediaLibrary) +ADD_SUBDIRECTORY(RvPackage) +ADD_SUBDIRECTORY(TwkQtBase) +ADD_SUBDIRECTORY(RvCommon) +ADD_SUBDIRECTORY(QTBundle) +ADD_SUBDIRECTORY(TwkApp) +ADD_SUBDIRECTORY(OutputVideoDevices) diff --git a/src/lib/app/DarwinBundle/CMakeLists.txt b/src/lib/app/DarwinBundle/CMakeLists.txt new file mode 100644 index 000000000..715ded9ff --- /dev/null +++ b/src/lib/app/DarwinBundle/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "DarwinBundle" +) + +FILE(GLOB _sources DarwinBundle.mm) + +ADD_LIBRARY( + ${_target} STATIC + ${_sources} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PUBLIC TwkApp + PRIVATE TwkUtil + TwkExc + stl_ext + Boost::headers + "-framework ApplicationServices" + "-framework AppKit" + "-framework Foundation" +) + +RV_STAGE(TYPE "LIBRARY" TARGET ${_target}) diff --git a/src/lib/app/DarwinBundle/DarwinBundle.mm b/src/lib/app/DarwinBundle/DarwinBundle.mm new file mode 100644 index 000000000..29cfbe294 --- /dev/null +++ b/src/lib/app/DarwinBundle/DarwinBundle.mm @@ -0,0 +1,747 @@ +//****************************************************************************** +// Copyright (c) 2007 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#import +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace boost; + +void (*sessionFromUrlPointer)(std::string) = 0; +void (*putUrlOnMacPasteboardPointer)(std::string, std::string) = 0; +void (*registerEventHandlerPointer)() = 0; + +void +putUrlOnMacPasteboard (std::string url, std::string title) +{ + NSString *titleString = [[NSString alloc] initWithUTF8String: title.c_str()]; + NSString *urlString = [[NSString alloc] initWithUTF8String: url.c_str()]; + NSString *fixedUrlString = (NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef) urlString, NULL, NULL, kCFStringEncodingUTF8); + + NSURL *nsurl = [NSURL URLWithString: fixedUrlString]; + + if (nsurl == nil) + { + std::cerr << "ERROR: malformed url: '" << url << "'" << std::endl; + return; + } + + const NSString* kUTTypeURLName = @"public.url-name"; + const NSString* kUTTypeURL = @"public.url"; + const NSString* kUTTypePlainText = @"public.utf8-plain-text"; + + NSPasteboard* pb = [NSPasteboard generalPasteboard]; + + [pb declareTypes:[NSArray arrayWithObjects: + NSURLPboardType, kUTTypeURLName, kUTTypeURL, kUTTypePlainText, nil] owner:nil]; + [nsurl writeToPasteboard:pb]; + [pb setString:titleString forType:(NSString*)kUTTypeURLName]; + [pb setString:fixedUrlString forType:(NSString*)kUTTypeURL]; + [pb setString:fixedUrlString forType:(NSString*)kUTTypePlainText]; + + std::cerr << "INFO: URL on pasteboard: '" << title << "' (" << url << ")" << std::endl; +} + +@interface UrlHandler: NSObject { +} +-(void) getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; +@end + +@implementation UrlHandler +- (void) getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent +{ + NSString *url = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + // std::cerr << "INFO: url received from apple event '" << [url cString] << "'" << std::endl; + + if (sessionFromUrlPointer) (*sessionFromUrlPointer)([url UTF8String]); +} +@end + +UrlHandler *urlHandler = 0; + +namespace TwkApp { +using namespace TwkUtil; +using namespace std; + +struct BundlePaths +{ + NSAutoreleasePool* pool; + NSString* appName; + NSBundle* bundle; + NSUserDefaults* defaults; + NSString* plugins; + NSArray* supportArray; + NSArray* homeSupport; +}; + +static void +mkpath(NSString* path) +{ + NSArray* array = [path pathComponents]; + int n = [array count]; + NSString* ppath = [array objectAtIndex: 0]; + + for (int i = 1; i < n; i++) + { + ppath = [ppath stringByAppendingPathComponent: [array objectAtIndex: i]]; + const char* utf8 = [ppath UTF8String]; + + if (!fileExists(utf8)) mkdir(utf8, 0777); + } +} + +static void +registerEventHandler() +{ + if (urlHandler) + { + // cerr << "INFO: registering rvlink url handler" << endl; + // + // Note: we _must_ remove previous handler here. It will + // be the internal Qt one as of 4.6, and doesn't work + // (conflates url open with file open and mangles the url). + // Apple docs say that setEventHandler removes previous + // handler, but that appears to be false. + // + [[NSAppleEventManager sharedAppleEventManager] removeEventHandlerForEventClass:kInternetEventClass andEventID:kAEGetURL]; + + [[NSAppleEventManager sharedAppleEventManager] setEventHandler:urlHandler + andSelector:@selector(getUrl:withReplyEvent:) forEventClass:kInternetEventClass + andEventID:kAEGetURL]; + } +} + +DarwinBundle::DarwinBundle(const FileName& appName, + size_t majv, + size_t minv, + size_t revn, + bool protocolHandler, + bool sandboxed, + bool inheritedSandbox) + : Bundle(appName, majv, minv, revn, sandboxed, inheritedSandbox) +{ + m_bundle = new BundlePaths(); + m_bundle->pool = [[NSAutoreleasePool alloc] init]; + m_bundle->appName = [[NSString alloc] initWithUTF8String: appName.c_str()]; + m_bundle->bundle = [NSBundle mainBundle]; + m_bundle->defaults = [NSUserDefaults standardUserDefaults]; + + bool hasSandboxedEnabled = sandboxed || inheritedSandbox; + + ostringstream str; + str << majv << "." << minv << "." << revn; + setEnvVar("TWK_APP_VERSION", str.str().c_str(), 1); + + NSString* versionStr = [[NSString alloc] initWithUTF8String: str.str().c_str()]; + + // + // Make sure ~/Library/Application Support/RV exists + // + + NSString* userSupport = [[NSString alloc] initWithUTF8String: getenv("HOME")]; + if (hasSandboxedEnabled) + { + userSupport = [userSupport stringByAppendingPathComponent: @"Library"]; + userSupport = [userSupport stringByAppendingPathComponent: @"Containers"]; + userSupport = [userSupport stringByAppendingPathComponent: @"com.tweaksoftware.crank"]; + userSupport = [userSupport stringByAppendingPathComponent: @"Data"]; + } + userSupport = [userSupport stringByAppendingPathComponent: @"Library"]; + userSupport = [userSupport stringByAppendingPathComponent: @"Application Support"]; + userSupport = [userSupport stringByAppendingPathComponent: @"RV"]; + m_userSupportPath = [userSupport UTF8String]; + + if (!fileExists([userSupport UTF8String])) + { + mkpath(userSupport); + } + + const char* rvSupportEnv = hasSandboxedEnabled ? NULL : getenv("RV_SUPPORT_PATH"); + const char* twkSupportEnv = hasSandboxedEnabled ? NULL : getenv("TWK_SUPPORT_PATH"); + + if (const char* c = rvSupportEnv ? rvSupportEnv : twkSupportEnv) + { + vector tokens; + stl_ext::tokenize(tokens, c, ":"); + + NSMutableArray* m = [[NSMutableArray alloc] init]; + + for (size_t i=0; i < tokens.size(); i++) + { + [m addObject: [NSString stringWithUTF8String: tokens[i].c_str()]]; + } + + [m addObject: [m_bundle->bundle builtInPlugInsPath]]; + + m_bundle->supportArray = m; + } + else + { + NSMutableArray* m = [[NSMutableArray alloc] initWithArray: + NSSearchPathForDirectoriesInDomains( + NSApplicationSupportDirectory, + NSAllDomainsMask, + YES)]; + + for (size_t i = 0; i < [m count]; i++) + { + NSString* s = [m objectAtIndex: i]; + [m replaceObjectAtIndex: i withObject: + [s stringByAppendingPathComponent: m_bundle->appName]]; + } + + [m addObject: [m_bundle->bundle builtInPlugInsPath]]; + m_bundle->supportArray = m; + } + + for (int i=0; i < [m_bundle->supportArray count]; i++) + { + NSString* path = [m_bundle->supportArray objectAtIndex: i]; + + if (fileExists([path UTF8String])) + { + NSString* mu = [path stringByAppendingPathComponent: @"Mu"]; + NSString* py = [path stringByAppendingPathComponent: @"Python"]; + NSString* imgformats = [path stringByAppendingPathComponent: @"ImageFormats"]; + NSString* nodes = [path stringByAppendingPathComponent: @"Nodes"]; + NSString* oiioplugs = [path stringByAppendingPathComponent: @"OIIO"]; + NSString* movformats = [path stringByAppendingPathComponent: @"MovieFormats"]; + NSString* supportfiles = [path stringByAppendingPathComponent: @"SupportFiles"]; + NSString* configfiles = [path stringByAppendingPathComponent: @"ConfigFiles"]; + NSString* lib = [path stringByAppendingPathComponent: @"lib"]; + NSString* packages = [path stringByAppendingPathComponent: @"Packages"]; + NSString* profiles = [path stringByAppendingPathComponent: @"Profiles"]; + + if (!hasSandboxedEnabled && !fileExists([packages UTF8String])) + { + mkpath(packages); + mkpath(mu); + mkpath(imgformats); + mkpath(oiioplugs); + mkpath(movformats); + mkpath(supportfiles); + mkpath(configfiles); + mkpath(lib); + mkpath(profiles); + } + + addPathToEnvVar("TWK_FB_PLUGIN_PATH", [imgformats UTF8String]); + addPathToEnvVar("TWK_NODE_PLUGIN_PATH", [nodes UTF8String]); + addPathToEnvVar("TWK_MOVIE_PLUGIN_PATH", [movformats UTF8String]); + addPathToEnvVar("TWK_PROFILE_PLUGIN_PATH", [profiles UTF8String]); + addPathToEnvVar("MU_MODULE_PATH", [mu UTF8String]); + addPathToEnvVar("PYTHONPATH", [py UTF8String]); + addPathToEnvVar("OIIO_LIBRARY_PATH.", [oiioplugs UTF8String]); + } + } + + [m_bundle->defaults removeVolatileDomainForName: NSArgumentDomain]; + [m_bundle->defaults setObject: @"NO" forKey: @"NSTreatUnknownArgumentsAsOpen"]; + + addPathToEnvVar("TWK_APP_SUPPORT_PATH", supportPath()); + + if (protocolHandler) + { + // + // rvlink protocol handling + // + + // Regsiter event handler + // + // cerr << "INFO: registering rvlink url handler" << endl; + urlHandler = [[UrlHandler alloc] init]; + registerEventHandler(); + registerEventHandlerPointer = registerEventHandler; + + // Make mac pasteboard copy available. + // + putUrlOnMacPasteboardPointer = putUrlOnMacPasteboard; + } + + // + // Non-deprecated way to get the OS minor version + // + + vector charbuffer(256); + size_t size = charbuffer.size(); + int ret = sysctlbyname("kern.osrelease", &charbuffer[0], &size, NULL, 0); + + vector buffer; + string r = &charbuffer[0]; + algorithm::split(buffer, r, is_any_of(string(".")), token_compress_on); + + size_t minor = std::atoi(buffer[0].c_str()) - 4; + + ostringstream minorStr; + minorStr << minor; + + setEnvVar("RV_OS_VERSION_MAJOR", "10"); + setEnvVar("RV_OS_VERSION_MINOR", minorStr.str()); + setEnvVar("RV_OS_VERSION_REVISION", "0"); +} + +DarwinBundle::~DarwinBundle() +{ + [m_bundle->pool release]; + delete m_bundle; +} + +void +DarwinBundle::registerHandler() +{ + // + // Claim to be the default protocol handler + // + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; + OSStatus result = LSSetDefaultHandlerForURLScheme((CFStringRef)@"rvlink", (CFStringRef)bundleID); + + //TODO: Check result for errors +} + +Bundle::Path +DarwinBundle::top() +{ + return Path([[m_bundle->bundle bundlePath] UTF8String]); +} + +Bundle::Path +DarwinBundle::resourcePath() +{ + return Path([[m_bundle->bundle resourcePath] UTF8String]); +} + +Bundle::Path +DarwinBundle::builtInPluginPath() +{ + return Path([[m_bundle->bundle builtInPlugInsPath] UTF8String]); +} + +Bundle::Path +DarwinBundle::resource(const FileName& name, const FileName& type) +{ + NSString* s = [[NSString alloc] initWithUTF8String: name.c_str()]; + NSString* t = [[NSString alloc] initWithUTF8String: type.c_str()]; + NSString* p = [m_bundle->bundle pathForResource: s ofType: t]; + return p ? Path([p UTF8String]) : ""; +} + +Bundle::Path +DarwinBundle::executableFile(const FileName& name) +{ + NSString* s = [[NSString alloc] initWithUTF8String: name.c_str()]; + NSString* p = [m_bundle->bundle pathForAuxiliaryExecutable: s]; + return p ? Path([p UTF8String]) : ""; +} + +Bundle::Path +DarwinBundle::application(const FileName& name) +{ + return resource(name, "app"); +} + +Bundle::PathVector +DarwinBundle::fileInSupportPath(const DirName& name) +{ + PathVector paths; + NSString* s = [[NSString alloc] initWithUTF8String: name.c_str()]; + NSString* mp = [[NSString alloc] initWithUTF8String: majorVersionDir().c_str()]; + NSString* vp = [[NSString alloc] initWithUTF8String: versionDir().c_str()]; + + // + // Specific version (e.g., Library/Application Support/App/3.6/name) + // + + for (int i=0; i < [m_bundle->supportArray count]; i++) + { + paths.push_back([[[[m_bundle->supportArray objectAtIndex: i] + stringByAppendingPathComponent: vp] + stringByAppendingPathComponent: s] + UTF8String]); + } + + // + // Major version (e.g., Library/Application Support/App/3/name) + // + + for (int i=0; i < [m_bundle->supportArray count]; i++) + { + paths.push_back([[[[m_bundle->supportArray objectAtIndex: i] + stringByAppendingPathComponent: mp] + stringByAppendingPathComponent: s] + UTF8String]); + } + + // + // Generic (e.g., Library/Application Support/App/name) + // + + for (int i=0; i < [m_bundle->supportArray count]; i++) + { + paths.push_back([[[m_bundle->supportArray objectAtIndex: i] + stringByAppendingPathComponent: s] + UTF8String]); + } + + return paths; +} + +Bundle::PathVector +DarwinBundle::supportPath() +{ + PathVector paths; + + for (int i=0; i < [m_bundle->supportArray count]; i++) + { + paths.push_back([[m_bundle->supportArray objectAtIndex: i] UTF8String]); + } + + return paths; +} + +Bundle::Path +DarwinBundle::defaultLicense(const FileName& licFileName, + const FileName& type) +{ + return "/Library/Application Support/RV/" + licFileName + "." + type; +} + +Bundle::PathVector +DarwinBundle::licenseFiles(const FileName& licFileName, + const FileName& type) +{ + FileName f = licFileName; + f += "."; + f += type; + PathVector paths = fileInSupportPath(f); + Path appfile = resource(licFileName, type); + paths.push_back(appfile); + Path dpath = defaultLicense(licFileName, type); + if (find(paths.begin(), paths.end(), dpath) == paths.end()) paths.push_back(dpath); + return paths; +} + + +Bundle::Path +DarwinBundle::rcfile(const FileName& rcfileName, + const FileName& type, + const EnvVar& rcenv) +{ + FileName rc = rcfileName; + rc += "."; + rc += type; + + FileName dotFile = "."; + dotFile += rc; + + NSString* dotString = [[NSString alloc] initWithUTF8String: dotFile.c_str()]; + NSString* rcString = [[NSString alloc] initWithUTF8String: rcfileName.c_str()]; + + NSString* home = NSHomeDirectory(); + NSString* homeInit = [home stringByAppendingPathComponent: dotString]; + NSFileManager* fm = [NSFileManager defaultManager]; + + if (const char* e = getenv(rcenv.c_str())) + { + return e; + } + + if ([fm fileExistsAtPath: homeInit]) + { + const char* initPath = [homeInit UTF8String]; + return initPath; + } + + Path binit = resource(rcfileName, type); + NSString* bs = [[NSString alloc] initWithUTF8String: binit.c_str()]; + + if ([fm fileExistsAtPath: bs]) + { + const char* initPath = [bs UTF8String]; + return initPath; + } + + return ""; +} + + +Bundle::PathVector +DarwinBundle::pluginPath(const DirName& pluginType) +{ + return fileInSupportPath(pluginType); +} + +Bundle::PathVector +DarwinBundle::scriptPath(const DirName& scriptType) +{ + return fileInSupportPath(scriptType); +} + +Bundle::Path +DarwinBundle::appPluginPath(const DirName& pluginType) +{ + Path p = builtInPluginPath(); + ostringstream str; + str << p; + if (p[p.size()-1] != '/') str << "/"; + str << pluginType; + return str.str(); +} + +Bundle::Path +DarwinBundle::appScriptPath(const DirName& scriptType) +{ + return appPluginPath(scriptType); +} + +Bundle::Path +DarwinBundle::userCacheDir() +{ + NSFileManager* fm = [NSFileManager defaultManager]; + + NSError* errorReturn; + + NSURL* url = [fm URLForDirectory: NSCachesDirectory + inDomain: NSUserDomainMask + appropriateForURL: nil + create: YES + error: nil]; + + if ([url isFileURL] == YES) + { + const char* utf8 = [[url path] UTF8String]; + return Path(utf8); + } + + return Path(); +} + +void +DarwinBundle::setEnvVar(const EnvVar& var, const Path& value, bool force) +{ + if (!getenv(var.c_str()) || force) + { + setenv(var.c_str(), value.c_str(), 1); + } +} + +void +DarwinBundle::addPathToEnvVar(const EnvVar& var, const PathVector& value) +{ + ostringstream str; + const char* envvar = getenv(var.c_str()); + if (envvar) str << envvar; + + for (int i=0; i < value.size(); i++) + { + if (i || envvar) str << ":"; + str << value[i]; + } + + setEnvVar(var, str.str(), true); +} + +void +DarwinBundle::addPathToEnvVar(const EnvVar& var, const Path& value, bool toHead) +{ + ostringstream str; + const char* envvar = getenv(var.c_str()); + + if (toHead) + { + str << value; + if (envvar) str << ":" << envvar; + } + else + { + if (envvar) str << envvar << ":"; + str << value; + } + setEnvVar(var, str.str(), true); +} + +void +DarwinBundle::removePSN(int &argc, char* argv[], vector& nargv) +{ + int eatArgs = 0; + + for (int i=0; i < argc; i++) + { + if (eatArgs > 0) + { + eatArgs--; + } + else if (!strncmp(argv[i], "-psn", 4)) + { + } + else if (!strncmp(argv[i], "-NS", 3)) + { + eatArgs = 1; + } + else + { + nargv.push_back(argv[i]); + } + } +} + +Bundle::FileName +DarwinBundle::crashLogFile() +{ + string lfile = [m_bundle->appName UTF8String]; + lfile += ".crash.log"; + return lfile; +} + +Bundle::FileName +DarwinBundle::crashLogDirectory() +{ + return "Library/Logs/CrashReporter/"; +} + +Bundle::Path +DarwinBundle::userPluginPath(const DirName& pluginType) +{ + return m_userSupportPath + "/" + pluginType; +} + +Bundle::FileAccessPermission +DarwinBundle::permissionForFileAccess(const Path& filePath, bool readonly) const +{ + if (isSandboxed()) + { + NSString* nsFilePath = [NSString stringWithUTF8String: filePath.c_str()]; + NSURL* url = [NSURL fileURLWithPath: nsFilePath]; + NSError* error = NULL; + + NSURLBookmarkCreationOptions options = NSURLBookmarkCreationWithSecurityScope | + (readonly ? NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess : 0) | + //NSURLBookmarkCreationSecurityScopeAllowOnlyReadAccess | + NSURLBookmarkCreationPreferFileIDResolution; + + NSData* data = [url bookmarkDataWithOptions: options + includingResourceValuesForKeys: nil + relativeToURL: nil + error: &error]; + + if (error) + { + NSString* desc = [error localizedDescription]; + ostringstream err; + err << "ERROR: " << [desc UTF8String]; + cout << err.str() << endl; + TWK_THROW_EXC_STREAM(err.str()); + } + + const void* bytes = [data bytes]; + NSUInteger sizeInBytes = [data length]; + + return TwkUtil::id64Encode((const char*)bytes, sizeInBytes); + } + else + { + FileAccessPermission permission; + return permission; + } + +} + +Bundle::AccessObject +DarwinBundle::beginFileAccessWithPermission(const FileAccessPermission& permission) const +{ + if (isSandboxed()) + { + if (permission.empty()) return AccessObject(0); + + vector decodeData; + TwkUtil::id64Decode(permission, decodeData); + + NSData* data = [NSData dataWithBytes: &decodeData.front() + length: decodeData.size()]; + + NSError* error = NULL; + BOOL stale = NO; + + NSURLBookmarkResolutionOptions options = NSURLBookmarkResolutionWithSecurityScope; + + NSURL* outURL = [NSURL URLByResolvingBookmarkData: data + options: options + relativeToURL: nil + bookmarkDataIsStale: &stale + error: &error]; + + if (error) + { + NSString* desc = [error localizedDescription]; + ostringstream err; + err << "ERROR: bookmark resolve failed: " << [desc UTF8String]; + cout << err.str() << endl; + TWK_THROW_EXC_STREAM(err.str()); + } + else if (!outURL) + { + // throw + TWK_THROW_EXC_STREAM("ERROR: Unable to resolve bookmark data to URL"); + } + + NSURL* fileURL = outURL; + [fileURL retain]; + [fileURL startAccessingSecurityScopedResource]; + return AccessObject(fileURL); + } + else + { + return AccessObject(0x1); + } +} + +void +DarwinBundle::endFileAccessWithPermission(AccessObject aobj) const +{ + if (isSandboxed() && aobj != AccessObject(0)) + { + NSURL* fileURL = (NSURL*)aobj; + [fileURL stopAccessingSecurityScopedResource]; + [fileURL release]; + } +} + +Bundle::Path +DarwinBundle::userHome() +{ + return Path([NSHomeDirectory() UTF8String]); +} + +Bundle::Path +DarwinBundle::userMovies() +{ + return Path([[NSHomeDirectory() stringByAppendingPathComponent: @"Movies"] UTF8String]); +} + +Bundle::Path +DarwinBundle::userMusic() +{ + return Path([[NSHomeDirectory() stringByAppendingPathComponent: @"Music"] UTF8String] ); +} + +Bundle::Path +DarwinBundle::userPictures() +{ + return Path([[NSHomeDirectory() stringByAppendingPathComponent: @"Pictures"] UTF8String] ); +} + +} // TwkApp diff --git a/src/lib/app/DarwinBundle/DarwinBundle/DarwinBundle.h b/src/lib/app/DarwinBundle/DarwinBundle/DarwinBundle.h new file mode 100644 index 000000000..fa932c164 --- /dev/null +++ b/src/lib/app/DarwinBundle/DarwinBundle/DarwinBundle.h @@ -0,0 +1,115 @@ +//****************************************************************************** +// Copyright (c) 2007 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifndef __DarwinBundle__DarwinBundle__h__ +#define __DarwinBundle__DarwinBundle__h__ +#include + +namespace TwkApp { + +// +// DarwinBundle implements the TwkApp::Bundle API for an NSApp. It +// basically calls the underlying NSBundle functions. +// +// This class will also create and AutoRelease pool. So if you make +// this on the stack in main it will provide ObjC memory management +// for you. +// +// If the bundle is created as sandboxed the security bookmark +// functions will return actual values. +// + +struct BundlePaths; + +class DarwinBundle : public Bundle +{ + public: + DarwinBundle(const FileName& AppName, + size_t majorVersion, + size_t minorVersion, + size_t revisionNumber, + bool protocolHandler = false, + bool sandboxed = false, + bool inheritedSandbox = false); + + virtual ~DarwinBundle(); + + // + // DarwinBundle API + // + + PathVector fileInSupportPath(const FileName& name); + + // + // Resource path, as in NSBundle call + // + + Path resourcePath(); + + Path builtInPluginPath(); + + // + // This is a helper function which gets rid of the -psn argument + // that the open command adds to the argument list. nargv + // contains the filtered arguments. + // + + static void removePSN(int &argc, char* argv[], std::vector& nargv); + + // + // Bundle API + // + + virtual Path userHome(); + virtual Path userMovies(); + virtual Path userMusic(); + virtual Path userPictures(); + virtual Path top(); + virtual Path executableFile(const FileName& name); + virtual Path application(const FileName& name); + virtual PathVector supportPath(); + virtual Path rcfile(const FileName& rcfileName, + const FileName& type, + const EnvVar& rcenv); + virtual Path userPluginPath(const DirName& pluginType); + virtual PathVector pluginPath(const DirName& pluginType); + virtual PathVector scriptPath(const DirName& scriptType); + virtual Path appPluginPath(const DirName& pluginType); + virtual Path appScriptPath(const DirName& scriptType); + virtual Path userCacheDir(); + virtual Path resource(const FileName& name, const FileName& type); + virtual PathVector licenseFiles(const FileName& licFileName, + const FileName& type); + virtual Path defaultLicense(const FileName& licFileName, + const FileName& type); + virtual void setEnvVar(const EnvVar& name, const Path& value, bool force=false); + virtual void addPathToEnvVar(const EnvVar& name, const PathVector& value); + virtual void addPathToEnvVar(const EnvVar& name, const Path& value, bool toHead=false); + virtual FileName crashLogFile(); + virtual Path crashLogDirectory(); + + virtual FileAccessPermission permissionForFileAccess(const Path&, bool readonly) const; + virtual AccessObject beginFileAccessWithPermission(const FileAccessPermission&) const; + virtual void endFileAccessWithPermission(AccessObject) const; + + // + // Register this executable as the default rvlink protocol + // handler. + // + + void registerHandler(); + + private: + BundlePaths* m_bundle; + std::string m_userSupportPath; +}; + + +} // TwkApp + +#endif // __DarwinBundle__DarwinBundle__h__ + diff --git a/src/lib/app/MuTwkApp/CMakeLists.txt b/src/lib/app/MuTwkApp/CMakeLists.txt new file mode 100644 index 000000000..93e19a89b --- /dev/null +++ b/src/lib/app/MuTwkApp/CMakeLists.txt @@ -0,0 +1,39 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "MuTwkApp" +) + +SET(_sources + CommandsModule.cpp + MuInterface.cpp + EventType.cpp + FunctionAction.cpp + MenuItem.cpp + MenuState.cpp + SettingsValueType.cpp +) + +ADD_LIBRARY( + ${_target} STATIC + ${_sources} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PUBLIC Mu MuLang TwkApp + PRIVATE TwkUtil stl_ext +) + +RV_STAGE(TYPE "LIBRARY" TARGET ${_target}) diff --git a/src/lib/app/MuTwkApp/CommandsModule.cpp b/src/lib/app/MuTwkApp/CommandsModule.cpp new file mode 100644 index 000000000..209e1193a --- /dev/null +++ b/src/lib/app/MuTwkApp/CommandsModule.cpp @@ -0,0 +1,1026 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#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 + +namespace TwkApp { +using namespace TwkApp; +using namespace Mu; +using namespace std; +using namespace TwkUtil; +typedef StringType::String String; +CommandsModule::CommandsModule(Context* c, const char* name) : Module(c, name) +{ + +} + +CommandsModule::~CommandsModule() +{ +} + +void +CommandsModule::load() +{ + USING_MU_FUNCTION_SYMBOLS; + typedef ParameterVariable Param; + + // + // Force the existance of the types we're going to use + // + + + MuLangContext* context = (MuLangContext*)globalModule()->context(); + const Type* stringType = context->stringType(); + const Type* floatType = context->floatType(); + const Type* intType = context->intType(); + const Type* vec2fType = context->vec2fType(); + Context* c = context; + + Context::TypeVector types; + types.push_back(context->stringType()); + types.push_back(context->intType()); + context->tupleType(types); + + context->arrayType(floatType, 1, 0); // float[] + context->arrayType(floatType, 2, 4, 4); // float[4,4] + context->arrayType(intType, 1, 0); // int[] + context->arrayType(stringType, 1, 0); // string[] + context->arrayType(vec2fType, 1, 0); + context->functionType("(void;Event)"); + + types.clear(); + types.push_back(stringType); + types.push_back(stringType); + + context->listType(stringType); + context->arrayType(context->tupleType(types), 1, 0); + + globalScope()->addSymbols( new MenuItem(c, "MenuItem", 0), + EndArguments ); + + addSymbols(new SymbolicConstant(c, "NeutralMenuState", "int", Value(0)), + new SymbolicConstant(c, "UncheckedMenuState", "int", Value(1)), + new SymbolicConstant(c, "CheckedMenuState", "int", Value(2)), + new SymbolicConstant(c, "MixedStateMenuState", "int", Value(3)), + new SymbolicConstant(c, "DisabledMenuState", "int", Value(-1)), + + new Function(c, "eval", CommandsModule::eval, None, + Parameters, + new Param(c, "text", "string"), + Return, "string", + End), + + new Function(c, "undo", CommandsModule::undo, None, + Return, "void", + End), + + new Function(c, "redo", CommandsModule::redo, None, + Return, "void", + End), + + new Function(c, "clearHistory", CommandsModule::clearHistory, None, + Return, "void", + End), + + new Function(c, "beginCompoundCommand", + CommandsModule::beginCompoundCommand, None, + Parameters, + new ParameterVariable(c, "name", "string"), + Return, "void", + End), + + new Function(c, "endCompoundCommand", + CommandsModule::endCompoundCommand, None, + Return, "void", + End), + + new Function(c, "undoHistory", CommandsModule::undoHistory, None, + Return, "string[]", + End), + + new Function(c, "redoHistory", CommandsModule::redoHistory, None, + Return, "string[]", + End), + + new Function(c, "activateMode", CommandsModule::activateMode, + None, + Return, "void", + Parameters, + new Param(c, "name", "string"), + End), + + new Function(c, "isModeActive", CommandsModule::isModeActive, + None, + Return, "bool", + Parameters, + new Param(c, "name", "string"), + End), + + new Function(c, "activeModes", CommandsModule::activeModes, + None, + Return, "[string]", + End), + + new Function(c, "deactivateMode", CommandsModule::deactivateMode, + None, + Return, "void", + Parameters, + new Param(c, "name", "string"), + End), + + new Function(c, "defineMinorMode", CommandsModule::defineMinorMode, + None, + Return, "void", + Parameters, + new Param(c, "name", "string"), + new Param(c, "sortKey", "string", Value(Pointer(0))), + new Param(c, "order", "int", Value(0)), + End), + + new Function(c, "bindingDocumentation", CommandsModule::bindDoc, None, + Return, "string", + Parameters, + new Param(c, "eventName", "string"), + new Param(c, "modeName", "string", Value()), + new Param(c, "tableName", "string", Value()), + End), + + new Function(c, "bindings", CommandsModule::bindings, None, + Return, "(string,string)[]", + End), + + new Function(c, "bind", CommandsModule::bind, None, + Return, "void", + Parameters, + new Param(c, "modeName", "string"), + new Param(c, "tableName", "string"), + new Param(c, "eventName", "string"), + new Param(c, "func", "(void;Event)"), + new Param(c, "description", "string", Value()), + End), + + new Function(c, "bindRegex", CommandsModule::bindRegex, None, + Return, "void", + Parameters, + new Param(c, "modeName", "string"), + new Param(c, "tableName", "string"), + new Param(c, "eventPattern", "string"), + new Param(c, "func", "(void;Event)"), + new Param(c, "description", "string", Value()), + End), + + new Function(c, "unbind", CommandsModule::unbind, None, + Return, "void", + Parameters, + new Param(c, "modeName", "string"), + new Param(c, "tableName", "string"), + new Param(c, "eventName", "string"), + End), + + new Function(c, "unbindRegex", CommandsModule::unbindRegex, None, + Return, "void", + Parameters, + new Param(c, "modeName", "string"), + new Param(c, "tableName", "string"), + new Param(c, "eventName", "string"), + End), + + new Function(c, "setEventTableBBox", CommandsModule::setTableBBox, None, + Return, "void", + Parameters, + new Param(c, "modeName", "string"), + new Param(c, "tableName", "string"), + new Param(c, "min", "vector float[2]"), + new Param(c, "max", "vector float[2]"), + End), + + new Function(c, "defineModeMenu", + CommandsModule::defineModeMenu, None, + Return, "void", + Parameters, + new Param(c, "mode", "string"), + new Param(c, "menu", "MenuItem[]"), + new Param(c, "strict", "bool", Value(false)), + End), + + // new Function(c, "isComputationInProgress", + // CommandsModule::isComputationInProgress, None, + // Return, "bool", + // End), + + // new Function(c, "computationMessage", + // CommandsModule::computationMessage, None, + // Return, "string", + // End), + + // new Function(c, "computationProgress", + // CommandsModule::computationProgress, None, + // Return, "float", + // End), + + // new Function(c, "computationElapsedTime", + // CommandsModule::computationElapsedTime, None, + // Return, "float", + // End), + + new Function(c, "pushEventTable", + CommandsModule::pushEventTable, None, + Return, "void", + Parameters, + new Param(c, "table", "string"), + End), + + new Function(c, "popEventTable", + CommandsModule::popEventTable, None, + Return, "void", + End), + + new Function(c, "popEventTable", + CommandsModule::popNamedEventTable, None, + Return, "void", + Parameters, + new Param(c, "table", "string"), + End), + + new Function(c, "activeEventTables", + CommandsModule::activeEventTables, None, + Return, "[string]", + End), + + new Function(c, "contractSequences", + CommandsModule::contractSeq, None, + Return, "string[]", + Parameters, + new Param(c, "files", "string[]"), + End), + + new Function(c, "sequenceOfFile", + CommandsModule::sequenceOfFile, None, + Return, "(string,int)", + Parameters, + new Param(c, "file", "string"), + End), + + EndArguments); +} + +static Document* currentDocument() +{ + if (Document* d = Document::eventDocument()) + { + return d; + } + else + { + return Document::activeDocument(); + } +} + +static void throwBadArgumentException(const Mu::Node& node, + Mu::Thread& thread, + std::string msg) +{ + ostringstream str; + const Mu::MuLangContext* context = + static_cast(thread.context()); + ExceptionType::Exception *e = + new ExceptionType::Exception(context->exceptionType()); + str << "in " << node.symbol()->fullyQualifiedName() << ": " << msg; + e->string() += str.str().c_str(); + thread.setException(e); + BadArgumentException exc; + exc.message() = e->string(); + throw exc; +} + + +NODE_IMPLEMENTATION(CommandsModule::eval, Pointer) +{ + Process* p = NODE_THREAD.process(); + const String *text = NODE_ARG_OBJECT(0, StringType::String); + const StringType* stype = + static_cast(NODE_THIS.type()); + MuLangContext* c = static_cast(p->context()); + Context::ModuleList modules; + String* s = stype->allocate(muEval(c, p, modules, text->c_str(), "commands.eval")); + NODE_RETURN(s); +} + +NODE_IMPLEMENTATION(CommandsModule::undo, void) +{ + Document *d = currentDocument(); + d->undoCommand(); +} + +NODE_IMPLEMENTATION(CommandsModule::redo, void) +{ + Document *d = currentDocument(); + d->redoCommand(); +} + +NODE_IMPLEMENTATION(CommandsModule::clearHistory, void) +{ + Document *d = currentDocument(); + d->clearHistory(); +} + +NODE_IMPLEMENTATION(CommandsModule::beginCompoundCommand, void) +{ + String *name = NODE_ARG_OBJECT(0, StringType::String); + Document *d = currentDocument(); + d->beginCompoundCommand(name->c_str()); +} + +NODE_IMPLEMENTATION(CommandsModule::endCompoundCommand, void) +{ + Document *d = currentDocument(); + d->endCompoundCommand(); +} + +NODE_IMPLEMENTATION(CommandsModule::readFile, void) +{ + const String *name = NODE_ARG_OBJECT(0, StringType::String); + bool import = NODE_ARG(1, bool); + + if (Document *d = currentDocument()) + { + Document::ReadRequest request; + request.setOption("append", import); + d->read(name->c_str(), request); + } +} + +NODE_IMPLEMENTATION(CommandsModule::writeFile, void) +{ + String* name = NODE_ARG_OBJECT(0, StringType::String); + const bool partial = NODE_ARG(1, bool); + + if (Document *d = currentDocument()) + { + Document::WriteRequest request; + request.setOption("partial", partial); + d->write(name->c_str(), request); + } +} + +namespace { + +void +compileHistoryDescriptions(const TwkApp::CommandHistory* history, + bool undo, + vector& array, + size_t indent = 0) +{ + const TwkApp::CommandStack& stack = undo ? history->undoStack() : history->redoStack(); + + for (int i=0; i < stack.size(); i++) + { + const TwkApp::Command* c = stack[i]; + + ostringstream str; + + for (size_t q = 0; q < indent; q++) str << "."; + str << c->description() << " (" << c->name() << ")"; + + array.push_back(str.str()); + + if (const TwkApp::CompoundCommand* cc = + dynamic_cast(stack[i])) + { + compileHistoryDescriptions(cc, undo, array, indent+1); + } + } +} + +} + +NODE_IMPLEMENTATION(CommandsModule::undoHistory, Mu::Pointer) +{ + Process *p = NODE_THREAD.process(); + Document *d = currentDocument(); + + const Mu::DynamicArrayType* atype = + static_cast(NODE_THIS.type()); + const Mu::StringType* stype = + static_cast(atype->elementType()); + + DynamicArray *array = new DynamicArray(atype, 1); + + vector desc; + compileHistoryDescriptions(d, true, desc); + array->resize(desc.size()); + + for (int i=0; i < desc.size(); i++) + { + array->element(i) = stype->allocate(desc[i]); + } + + NODE_RETURN(array); +} + +NODE_IMPLEMENTATION(CommandsModule::redoHistory, Mu::Pointer) +{ + Process *p = NODE_THREAD.process(); + Document *d = currentDocument(); + + const Mu::DynamicArrayType* atype = + static_cast(NODE_THIS.type()); + const Mu::StringType* stype = + static_cast(atype->elementType()); + + DynamicArray *array = new DynamicArray(atype, 1); + const TwkApp::CommandStack& redoStack = d->redoStack(); + array->resize(redoStack.size()); + + for (int i=0; i < redoStack.size(); i++) + { + const TwkApp::Command* c = redoStack[i]; + array->element(i) = stype->allocate(c->description()); + } + + NODE_RETURN(array); +} + +NODE_IMPLEMENTATION(CommandsModule::defineMinorMode, void) +{ + Process* p = NODE_THREAD.process(); + Document* d = currentDocument(); + String* modeName = NODE_ARG_OBJECT(0, StringType::String); + String* sortKey = NODE_ARG_OBJECT(1, StringType::String); + int order = NODE_ARG(2, int); + + MinorMode* mode = new MinorMode(modeName->c_str(), d); + mode->setOrder(order); + if (sortKey) mode->setSortKey(sortKey->c_str()); + d->addMode(mode); +} + +NODE_IMPLEMENTATION(CommandsModule::activateMode, void) +{ + Process* p = NODE_THREAD.process(); + Document* d = currentDocument(); + String* modeName = NODE_ARG_OBJECT(0, StringType::String); + + if (!modeName) + throwBadArgumentException(NODE_THIS, NODE_THREAD, "Nil argument to function"); + + d->activateMode(modeName->c_str()); +} + +NODE_IMPLEMENTATION(CommandsModule::isModeActive, bool) +{ + Process* p = NODE_THREAD.process(); + Document* d = currentDocument(); + bool b = false; + + if (String* modeName = NODE_ARG_OBJECT(0, StringType::String)) + { + b = d->isModeActive(modeName->c_str()); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "Nil argument to function"); + } + + NODE_RETURN(b); +} + + +NODE_IMPLEMENTATION(CommandsModule::deactivateMode, void) +{ + Process* p = NODE_THREAD.process(); + Document* d = currentDocument(); + String* modeName = NODE_ARG_OBJECT(0, StringType::String); + d->deactivateMode(modeName->c_str()); +} + +NODE_IMPLEMENTATION(CommandsModule::bind, void) +{ + Process *p = NODE_THREAD.process(); + Document *d = currentDocument(); + + String* modeName = NODE_ARG_OBJECT(0, StringType::String); + String* tableName = NODE_ARG_OBJECT(1, StringType::String); + String* eventName = NODE_ARG_OBJECT(2, StringType::String); + FunctionObject* obj = NODE_ARG_OBJECT(3, FunctionObject); + String* docstring = NODE_ARG_OBJECT(4, StringType::String); + + string doc = docstring ? docstring->c_str() : ""; + + if (Mode* mode = d->findModeByName(modeName->c_str())) + { + EventTable* table = mode->findTableByName(tableName->c_str()); + + if (!table) + { + table = new EventTable(tableName->c_str()); + mode->addEventTable(table); + } + + MuFuncAction* action = new MuFuncAction(obj, doc); + table->bind(eventName->c_str(), action); + // + // currentDocument has a copy of this table, so invalidate + // that copy. + // + d->invalidateEventTables(); + } + else + { + string msg = "No mode named " + string(modeName->c_str()); + throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); + } +} + +NODE_IMPLEMENTATION(CommandsModule::bindRegex, void) +{ + Process *p = NODE_THREAD.process(); + Document *d = currentDocument(); + + String* modeName = NODE_ARG_OBJECT(0, StringType::String); + String* tableName = NODE_ARG_OBJECT(1, StringType::String); + String* eventRegex = NODE_ARG_OBJECT(2, StringType::String); + FunctionObject* obj = NODE_ARG_OBJECT(3, FunctionObject); + String* docstring = NODE_ARG_OBJECT(4, StringType::String); + + string doc = docstring ? docstring->c_str() : ""; + + if (Mode* mode = d->findModeByName(modeName->c_str())) + { + EventTable* table = mode->findTableByName(tableName->c_str()); + + if (!table) + { + table = new EventTable(tableName->c_str()); + mode->addEventTable(table); + } + + MuFuncAction* action = new MuFuncAction(obj, doc); + table->bindRegex(eventRegex->c_str(), action); + // + // currentDocument has a copy of this table, so invalidate + // that copy. + // + d->invalidateEventTables(); + } + else + { + string msg = "No mode named " + string(modeName->c_str()); + throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); + } +} + +NODE_IMPLEMENTATION(CommandsModule::unbind, void) +{ + Process *p = NODE_THREAD.process(); + Document *d = currentDocument(); + + String* modeName = NODE_ARG_OBJECT(0, StringType::String); + String* tableName = NODE_ARG_OBJECT(1, StringType::String); + String* eventName = NODE_ARG_OBJECT(2, StringType::String); + + if (!modeName || !tableName || !eventName) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "Nil argument to function"); + } + + if (Mode* mode = d->findModeByName(modeName->c_str())) + { + if (EventTable* table = mode->findTableByName(tableName->c_str())) + { + table->unbind(eventName->c_str()); + // + // currentDocument has a copy of this table, so invalidate + // that copy. + // + d->invalidateEventTables(); + } + else + { + string msg = "No table named " + string(tableName->c_str()); + throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); + } + } + else + { + string msg = "No mode named " + string(modeName->c_str()); + throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); + } +} + +NODE_IMPLEMENTATION(CommandsModule::unbindRegex, void) +{ + Process *p = NODE_THREAD.process(); + Document *d = currentDocument(); + + String* modeName = NODE_ARG_OBJECT(0, StringType::String); + String* tableName = NODE_ARG_OBJECT(1, StringType::String); + String* eventName = NODE_ARG_OBJECT(2, StringType::String); + + if (!modeName || !tableName || !eventName) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "Nil argument to function"); + } + + if (Mode* mode = d->findModeByName(modeName->c_str())) + { + if (EventTable* table = mode->findTableByName(tableName->c_str())) + { + table->unbindRegex(eventName->c_str()); + // + // currentDocument has a copy of this table, so invalidate + // that copy. + // + d->invalidateEventTables(); + } + else + { + string msg = "No table named " + string(tableName->c_str()); + throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); + } + } + else + { + string msg = "No mode named " + string(modeName->c_str()); + throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); + } +} + +NODE_IMPLEMENTATION(CommandsModule::bindings, Mu::Pointer) +{ + Process *p = NODE_THREAD.process(); + Document *d = currentDocument(); + const Mu::MuLangContext* c = + static_cast(NODE_THREAD.context()); + + const DynamicArrayType* atype = (const DynamicArrayType*)NODE_THIS.type(); + const Class* ttype = (const Class*)atype->elementType(); + DynamicArray *array = new DynamicArray(atype, 1); + + struct StringTuple + { + String* a; + String* b; + }; + + const Document::EventTableStack& stack = d->eventTableStack(); + + for (int i = stack.size() - 1; i >= 0; i--) + { + const EventTable* table = stack[i]; + typedef EventTable::BindingMap::const_iterator iter; + typedef EventTable::BindingMap::value_type vtype; + + for (iter i = table->begin(); i != table->end(); ++i) + { + + String* name = c->stringType()->allocate(i->first); + String* doc = c->stringType()->allocate(i->second->docString()); + + ClassInstance* tuple = ClassInstance::allocate(ttype); + StringTuple* st = reinterpret_cast(tuple->structure()); + + st->a = name; + st->b = doc; + + array->resize(array->size() + 1); + array->element(array->size()-1) = tuple; + } + } + + NODE_RETURN(array); +} + +NODE_IMPLEMENTATION(CommandsModule::bindDoc, Mu::Pointer) +{ + Process *p = NODE_THREAD.process(); + Document *d = currentDocument(); + const Mu::MuLangContext* c = + static_cast(NODE_THREAD.context()); + + String* eventName = NODE_ARG_OBJECT(0, StringType::String); + String* modeName = NODE_ARG_OBJECT(1, StringType::String); + String* tableName = NODE_ARG_OBJECT(2, StringType::String); + + if (modeName && tableName) + { + if (Mode* mode = d->findModeByName(modeName->c_str())) + { + const EventTable* table = mode->findTableByName(tableName->c_str()); + + if (!table) + { + string msg = "No table named " + string(tableName->c_str()); + throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); + } + + if (const Action* action = table->query(eventName->c_str())) + { + String* doc = c->stringType()->allocate(action->docString()); + NODE_RETURN(doc); + } + else + { + string msg = "Not bound: " + string(eventName->c_str()); + throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); + } + } + else + { + string msg = "No mode named " + string(modeName->c_str()); + throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); + } + } + else + { + const Document::EventTableStack& stack = d->eventTableStack(); + + for (int i = stack.size() - 1; i >= 0; i--) + { + const EventTable* table = stack[i]; + + if (const Action* action = table->query(eventName->c_str())) + { + String* doc = c->stringType()->allocate(action->docString()); + NODE_RETURN(doc); + } + } + } + + NODE_RETURN(0); +} + +NODE_IMPLEMENTATION(CommandsModule::setTableBBox, void) +{ + Process* p = NODE_THREAD.process(); + Document* d = currentDocument(); + String* modeName = NODE_ARG_OBJECT(0, StringType::String); + String* tableName = NODE_ARG_OBJECT(1, StringType::String); + Vector2f min = NODE_ARG(2, Vector2f); + Vector2f max = NODE_ARG(3, Vector2f); + + TwkMath::Box2i bbox; + bbox.min.x = int(min[0]); + bbox.min.y = int(min[1]); + bbox.max.x = int(max[0]); + bbox.max.y = int(max[1]); + + if (Mode* mode = d->findModeByName(modeName->c_str())) + { + EventTable* table = mode->findTableByName(tableName->c_str()); + + if (!table) + { + table = new EventTable(tableName->c_str()); + mode->addEventTable(table); + } + + TwkMath::Box2i old = table->bbox(); + if (old != bbox) + { + table->setBBox(bbox); + // + // currentDocument has a copy of this table, so invalidate + // that copy. + // + d->invalidateEventTables(); + } + } + else + { + string msg = "No mode named " + string(modeName->c_str()); + throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); + } +} + +NODE_IMPLEMENTATION(CommandsModule::defineModeMenu, void) +{ + StringType::String* modeName = NODE_ARG_OBJECT(0, StringType::String); + DynamicArray* array = NODE_ARG_OBJECT(1, DynamicArray); + bool strict = NODE_ARG(2 ,bool); + + if (array) + { + Document* d = currentDocument(); + Menu* menu = createTwkAppMenu("Main", array); + + if (Mode* m = d->findModeByName(modeName->c_str())) + { + if (strict) m->setMenu(menu); + else m->merge(menu); + + // + // If we are re-defining an existing menu, we need to invalidate + // the old menu. + // + d->invalidateMenu(); + } + else + { + string msg = "No mode named " + string(modeName->c_str()); + throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); + } + } +} + +// NODE_IMPLEMENTATION(CommandsModule::isComputationInProgress, bool) +// { +// NODE_RETURN(Interrupt::isWorking()); +// } + +// NODE_IMPLEMENTATION(CommandsModule::computationMessage, Mu::Pointer) +// { +// Process *p = NODE_THREAD.process(); +// const Mu::StringType* stype = +// static_cast(NODE_THIS.type()); + +// if (!Interrupt::isWorking()) +// { +// string msg = "computation not in progress: invalid call"; +// throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); +// } + +// String* s = stype->allocate(Interrupt::currentComputation().message); +// NODE_RETURN(s); +// } + +// NODE_IMPLEMENTATION(CommandsModule::computationProgress, float) +// { +// if (!Interrupt::isWorking()) +// { +// string msg = "computation not in progress: invalid call"; +// throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); +// } + +// NODE_RETURN(Interrupt::currentComputation().percentDone); +// } + +// NODE_IMPLEMENTATION(CommandsModule::computationElapsedTime, float) +// { +// if (!Interrupt::isWorking()) +// { +// string msg = "computation not in progress: invalid call"; +// throwBadArgumentException(NODE_THIS, NODE_THREAD, msg); +// } + +// NODE_RETURN(Interrupt::currentComputation().timer.elapsed()); +// } + +NODE_IMPLEMENTATION(CommandsModule::pushEventTable, void) +{ + String* name = NODE_ARG_OBJECT(0, StringType::String); + Document* d = currentDocument(); + d->pushTable(name->c_str()); +} + +NODE_IMPLEMENTATION(CommandsModule::popEventTable, void) +{ + Document* d = currentDocument(); + d->popTable(); +} + +NODE_IMPLEMENTATION(CommandsModule::popNamedEventTable, void) +{ + String* name = NODE_ARG_OBJECT(0, StringType::String); + Document* d = currentDocument(); + d->popTable(name->c_str()); +} + +NODE_IMPLEMENTATION(CommandsModule::activeEventTables, Mu::Pointer) +{ + Document* d = currentDocument(); + const Document::EventTableStack& tables = d->eventTableStack(); + + Process *p = NODE_THREAD.process(); + const ListType* ltype = static_cast(NODE_THIS.type()); + const Mu::StringType* stype = + static_cast(ltype->elementType()); + Mu::List list(p, ltype); + + for (int i=0; i < tables.size(); i++) + { + StringType::String* str = stype->allocate(tables[i]->name()); + list.append(str); + } + + NODE_RETURN(list.head()); +} + +NODE_IMPLEMENTATION(CommandsModule::contractSeq, Pointer) +{ + Process* p = NODE_THREAD.process(); + const DynamicArrayType* atype = static_cast(NODE_THIS.type()); + const StringType* stype = static_cast(atype->elementType()); + DynamicArray* inArray = NODE_ARG_OBJECT(0, DynamicArray); + + if (!inArray) + throwBadArgumentException(NODE_THIS, NODE_THREAD, "Nil argument to function"); + + if (!inArray->size()) NODE_RETURN(inArray); + + DynamicArray* outArray = new DynamicArray(atype, 1); + FileNameList files(inArray->size()); + + for (int i=0; i < files.size(); i++) + { + files[i] = inArray->element(i)->c_str(); + } + + SequenceNameList seqlist = sequencesInFileList(files, GlobalExtensionPredicate); + + outArray->resize(seqlist.size()); + + for (int i=0; i < seqlist.size(); i++) + { + outArray->element(i) = stype->allocate(seqlist[i]); + } + + NODE_RETURN(outArray); +} + +NODE_IMPLEMENTATION(CommandsModule::activeModes, Pointer) +{ + Process* p = NODE_THREAD.process(); + const Mu::MuLangContext* c = + static_cast(NODE_THREAD.context()); + + const Mu::ListType* listType = static_cast(NODE_THIS.type()); + const StringType* stype = static_cast(listType->elementType()); + Document* d = currentDocument(); + + List list(p, listType); + list.append(stype->allocate(d->majorMode()->name())); + + const Document::MinorModes& minorModes = d->minorModes(); + + for (Document::MinorModes::const_iterator i = minorModes.begin(); + i != minorModes.end(); + ++i) + { + const MinorMode* m = *i; + list.append(stype->allocate(m->name())); + } + + NODE_RETURN(list.head()); +} + +NODE_IMPLEMENTATION(CommandsModule::sequenceOfFile, Pointer) +{ + Process* p = NODE_THREAD.process(); + String* file = NODE_ARG_OBJECT(0, StringType::String); + const StringType* stype = static_cast(file->type()); + const Class* ttype = static_cast(NODE_THIS.type()); + + struct PatTuple + { + StringType::String* file; + int frame; + }; + + string filename = file->c_str(); + PatternFramePair pp = TwkUtil::sequenceOfFile(filename, GlobalExtensionPredicate); + + ClassInstance* tuple = ClassInstance::allocate(ttype); + PatTuple* pt = reinterpret_cast(tuple->structure()); + pt->file = stype->allocate(pp.first); + pt->frame = pp.second; + + NODE_RETURN(tuple); +} + +} // MuTwkApp diff --git a/src/lib/app/MuTwkApp/EventType.cpp b/src/lib/app/MuTwkApp/EventType.cpp new file mode 100644 index 000000000..e7321150a --- /dev/null +++ b/src/lib/app/MuTwkApp/EventType.cpp @@ -0,0 +1,995 @@ +//****************************************************************************** +// Copyright (c) 2004 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** + +#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 + +namespace TwkApp { +using namespace Mu; +using namespace std; +typedef StringType::String String; + +//---------------------------------------------------------------------- + +EventType::EventInstance::EventInstance(const Class *c) : ClassInstance(c), + event(0), + document(0) +{ +} + +//---------------------------------------------------------------------- + + +EventType::EventType(Context* c, Class *super) : Class(c, "Event", super) +{ +} + +EventType::~EventType() {} + +Object* +EventType::newObject() const +{ + return new EventInstance(this); +} + +void +EventType::deleteObject(Object *obj) const +{ + delete static_cast(obj); +} + +void +EventType::outputValue(ostream &o, const Value &value, bool full) const +{ + ValueOutputState state(o, full); + outputValueRecursive(o, ValuePointer(&value._Pointer), state); +} + +void +EventType::outputValueRecursive(ostream &o, + const ValuePointer vp, + ValueOutputState& state) const +{ + EventInstance *i = *reinterpret_cast(vp); + + if (i) + { + o << "event) o << "\"" << *i->event << "\""; + else o << "nil"; + + o << ">"; + } + else + { + o << "nil"; + } +} + +void +EventType::load() +{ + USING_MU_FUNCTION_SYMBOLS; + + Symbol *s = scope(); + MuLangContext* context = + (MuLangContext*)globalModule()->context(); + Context* c = context; + + string tname = "Event"; + string rname = tname + "&"; + + const char* tn = tname.c_str(); + const char* rn = rname.c_str(); + + s->addSymbols( new ReferenceType(c, "Event&", this), + + new Function(c, "Event", BaseFunctions::dereference, Cast, + Return, tn, + Args, rn, End), + + EndArguments); + + globalScope()->addSymbols( + + new Function(c, "print", EventType::print, None, + Return, "void", + Args, tn, End), + + new Function(c, "=", BaseFunctions::assign, AsOp, + Return, rn, + Args, rn, tn, End), + + EndArguments); + + addSymbols( new Function(c, "pointer", EventType::pointer, None, + Return, "vector float[2]", + Args, tn, End), + + new Function(c, "relativePointer", + EventType::relativePointer, None, + Return, "vector float[2]", + Args, tn, End), + + new Function(c, "reference", EventType::reference, None, + Return, "vector float[2]", + Args, tn, End), + + new Function(c, "domain", EventType::domain, None, + Return, "vector float[2]", + Args, tn, End), + + new Function(c, "subDomain", EventType::subDomain, None, + Return, "vector float[2]", + Args, tn, End), + + new Function(c, "domainVerticalFlip", EventType::domainVerticalFlip, None, + Return, "bool", + Args, tn, End), + + new Function(c, "buttons", EventType::buttons, None, + Return, "int", + Args, tn, End), + + new Function(c, "modifiers", EventType::modifiers, None, + Return, "int", + Args, tn, End), + + new Function(c, "key", EventType::key, None, + Return, "int", + Args, tn, End), + + new Function(c, "name", EventType::name, None, + Return, "string", + Args, tn, End), + + new Function(c, "contents", EventType::contents, None, + Return, "string", + Args, tn, End), + + new Function(c, "contentsArray", EventType::contentsArray, None, + Return, "string[]", + Args, tn, End), + + new Function(c, "returnContents", EventType::returnContents, None, + Return, "string", + Args, tn, End), + + new Function(c, "dataContents", EventType::dataContents, None, + Return, "byte[]", + Args, tn, End), + + new Function(c, "sender", EventType::sender, None, + Return, "string", + Args, tn, End), + + new Function(c, "contentType", EventType::contentType, None, + Return, "int", + Args, tn, End), + + new Function(c, "contentMimeType", EventType::contentMimeType, None, + Return, "string", + Args, tn, End), + + new Function(c, "timeStamp", EventType::timeStamp, None, + Return, "float", + Args, tn, End), + + new Function(c, "reject", EventType::reject, None, + Return, "void", + Args, tn, End), + + new Function(c, "setReturnContent", EventType::setReturnContent, + None, + Return, "void", + Args, tn, "string", End), + + new Function(c, "pressure", EventType::pressure, + None, + Return, "float", + Args, tn, End), + + new Function(c, "tangentialPressure", EventType::tangentialPressure, + None, + Return, "float", + Args, tn, End), + + new Function(c, "rotation", EventType::rotation, + None, + Return, "float", + Args, tn, End), + + new Function(c, "xTilt", EventType::xTilt, + None, + Return, "int", + Args, tn, End), + + new Function(c, "yTilt", EventType::yTilt, + None, + Return, "int", + Args, tn, End), + + new Function(c, "activationTime", EventType::activationTime, + None, + Return, "float", + Args, tn, End), + + new SymbolicConstant(c, "None", "int", Value(int(0))), + new SymbolicConstant(c, "Shift", "int", Value(int(1 << 0))), + new SymbolicConstant(c, "Control", "int", Value(int(1 << 1))), + new SymbolicConstant(c, "Alt", "int", Value(int(1 << 2))), + new SymbolicConstant(c, "Meta", "int", Value(int(1 << 3))), + new SymbolicConstant(c, "Super", "int", Value(int(1 << 4))), + new SymbolicConstant(c, "CapLock", "int", Value(int(1 << 5))), + new SymbolicConstant(c, "NumLock", "int", Value(int(1 << 6))), + new SymbolicConstant(c, "ScrollLock", "int", Value(int(1 << 7))), + + new SymbolicConstant(c, "Button1", "int", Value(1 << 0)), + new SymbolicConstant(c, "Button2", "int", Value(1 << 1)), + new SymbolicConstant(c, "Button3", "int", Value(1 << 2)), + + new SymbolicConstant(c, "UnknownObject", "int", Value(-1)), + new SymbolicConstant(c, "BadObject", "int", Value(0)), + new SymbolicConstant(c, "FileObject", "int", Value(1)), + new SymbolicConstant(c, "URLObject", "int", Value(2)), + new SymbolicConstant(c, "TextObject", "int", Value(3)), + + EndArguments ); +} + +static void throwBadArgumentException(const Mu::Node& node, + Mu::Thread& thread, + std::string msg) +{ + ostringstream str; + const Mu::MuLangContext* context = + static_cast(thread.context()); + ExceptionType::Exception *e = + new ExceptionType::Exception(context->exceptionType()); + str << "in " << node.symbol()->fullyQualifiedName() << ": " << msg; + e->string() += str.str().c_str(); + thread.setException(e); + throw BadArgumentException(thread, e); +} + +NODE_IMPLEMENTATION(EventType::print, void) +{ + EventInstance *i = NODE_ARG_OBJECT(0, EventInstance); + + if (i) + { + i->type()->outputValue(cout, Value(i)); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } +} + +NODE_IMPLEMENTATION(EventType::name, Pointer) +{ + Process* p = NODE_THREAD.process(); + EventInstance *i = NODE_ARG_OBJECT(0, EventInstance); + const StringType* stype = + static_cast(NODE_THIS.type()); + + if (i) + { + NODE_RETURN(stype->allocate(i->event->name())); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + NODE_RETURN(0); + } +} + +NODE_IMPLEMENTATION(EventType::contentType, int) +{ + Process* p = NODE_THREAD.process(); + EventInstance *i = NODE_ARG_OBJECT(0, EventInstance); + + if (!i) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + int rval = 0; + + if (const DragDropEvent* de = + dynamic_cast(i->event)) + { + switch (de->contentType()) + { + case DragDropEvent::File: rval = 1; break; + case DragDropEvent::URL: rval = 2; break; + case DragDropEvent::Text: rval = 3; break; + default: rval = -1; break; + } + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } + + NODE_RETURN(rval); +} + +NODE_IMPLEMENTATION(EventType::contentMimeType, Pointer) +{ + EventInstance *i = NODE_ARG_OBJECT(0, EventInstance); + const StringType* stype = static_cast(NODE_THIS.type()); + String* s = 0; + + if (!i) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const RawDataEvent* e = + dynamic_cast(i->event)) + { + s = stype->allocate(e->contentType()); + } + else if (const GenericStringEvent* e = + dynamic_cast(i->event)) + { + s = stype->allocate("text/plain"); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "mime type not applicable"); + } + + NODE_RETURN(s); +} + +NODE_IMPLEMENTATION(EventType::contents, Pointer) +{ + Process* p = NODE_THREAD.process(); + EventInstance *i = NODE_ARG_OBJECT(0, EventInstance); + + if (!i) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + const StringType* stype = + static_cast(NODE_THIS.type()); + + String* s = 0; + + if (const DragDropEvent* de = + dynamic_cast(i->event)) + { + s = stype->allocate(de->stringContent()); + } + else if (const GenericStringEvent* e = + dynamic_cast(i->event)) + { + s = stype->allocate(e->stringContent()); + } + else if (const RawDataEvent* e = + dynamic_cast(i->event)) + { + if (e->utf8()) + { + s = stype->allocate(e->utf8()); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "event not text"); + } + } + else if (const RenderEvent* re = + dynamic_cast(i->event)) + { + s = stype->allocate(re->stringContent()); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } + + NODE_RETURN(s); +} + +NODE_IMPLEMENTATION(EventType::contentsArray, Pointer) +{ + Process* p = NODE_THREAD.process(); + MuLangContext* c = static_cast(p->context()); + EventInstance *i = NODE_ARG_OBJECT(0, EventInstance); + + if (!i) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + const DynamicArrayType* atype = + static_cast(NODE_THIS.type()); + DynamicArray* array = new DynamicArray(atype, 1); + + if (const GenericStringEvent* e = + dynamic_cast(i->event)) + { + const Event::StringVector& v = e->stringContentVector(); + const size_t n = v.size(); + + if (n == 0) + { + array->resize(1); + array->element(0) = c->stringType()->allocate(e->stringContent()); + } + else + { + array->resize(n); + + for (size_t q = 0; q < n; q++) + { + array->element(q) = c->stringType()->allocate(v[q]); + } + } + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } + + NODE_RETURN(array); +} + +NODE_IMPLEMENTATION(EventType::returnContents, Pointer) +{ + Process* p = NODE_THREAD.process(); + EventInstance *i = NODE_ARG_OBJECT(0, EventInstance); + + if (!i) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + const StringType* stype = + static_cast(NODE_THIS.type()); + + String* s = 0; + + if (const GenericStringEvent* e = + dynamic_cast(i->event)) + { + s = stype->allocate(e->returnContent()); + } + + NODE_RETURN(s); +} + +NODE_IMPLEMENTATION(EventType::dataContents, Pointer) +{ + EventInstance *inst = NODE_ARG_OBJECT(0, EventInstance); + DynamicArrayType* type = (DynamicArrayType*)NODE_THIS.type(); + DynamicArray *array = new DynamicArray(type, 1); + + if (!inst) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const RawDataEvent* e = dynamic_cast(inst->event)) + { + int sz = e->rawDataSize(); + array->resize(sz); + for (int i = 0; i < sz; ++i) array->element(i) = e->rawData()[i]; + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } + + NODE_RETURN(array); +} + +NODE_IMPLEMENTATION(EventType::interp, Pointer) +{ + EventInstance *inst = NODE_ARG_OBJECT(0, EventInstance); + const StringType* stype = static_cast(NODE_THIS.type()); + String* s = 0; + + if (!inst) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const RawDataEvent* e = dynamic_cast(inst->event)) + { + s = stype->allocate(e->contentType()); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "interp not applicable"); + } + + NODE_RETURN(s); +} + +NODE_IMPLEMENTATION(EventType::sender, Pointer) +{ + Process* p = NODE_THREAD.process(); + EventInstance *i = NODE_ARG_OBJECT(0, EventInstance); + const StringType* stype = + static_cast(NODE_THIS.type()); + + if (!i) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + String* s = 0; + + if (const GenericStringEvent* e = + dynamic_cast(i->event)) + { + s = stype->allocate(e->senderName()); + } + else + if (const RawDataEvent* e = + dynamic_cast(i->event)) + { + s = stype->allocate(e->senderName()); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } + + + NODE_RETURN(s); +} + +NODE_IMPLEMENTATION(EventType::key, int) +{ + EventInstance *e = NODE_ARG_OBJECT(0, EventInstance); + int k; + + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const KeyEvent* ke = + dynamic_cast(e->event)) + { + k = ke->key(); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } + + NODE_RETURN(k); +} + +NODE_IMPLEMENTATION(EventType::pointer, Vector2f) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + Vector2f v; + + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const TabletEvent* te = + dynamic_cast(e->event)) + { + v[0] = te->gx(); + v[1] = te->gy(); + } + else if (const PointerEvent* pe = + dynamic_cast(e->event)) + { + v[0] = pe->x(); + v[1] = pe->y(); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } + + return v; +} + +NODE_IMPLEMENTATION(EventType::relativePointer, Vector2f) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + Vector2f v; + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const TabletEvent* te = + dynamic_cast(e->event)) + { + v[0] = te->gx(); + v[1] = te->gy(); + + if (te->eventTable()) + { + v[0] -= te->eventTable()->bbox().min.x; + v[1] -= te->eventTable()->bbox().min.y; + } + } + else if (const PointerEvent* pe = + dynamic_cast(e->event)) + { + v[0] = pe->x(); + v[1] = pe->y(); + + if (pe->eventTable()) + { + v[0] -= pe->eventTable()->bbox().min.x; + v[1] -= pe->eventTable()->bbox().min.y; + } + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } + + return v; +} + +NODE_IMPLEMENTATION(EventType::reference, Vector2f) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + Vector2f v; + + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const PointerEvent* pe = + dynamic_cast(e->event)) + { + v[0] = pe->startX(); + v[1] = pe->startY(); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } + + return v; +} + +NODE_IMPLEMENTATION(EventType::domain, Vector2f) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + Vector2f v; + + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const PointerEvent* pe = + dynamic_cast(e->event)) + { + v[0] = pe->w(); + v[1] = pe->h(); + } + else if (const RenderEvent* re = + dynamic_cast(e->event)) + { + v[0] = re->w(); + v[1] = re->h(); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } + + return v; +} + +NODE_IMPLEMENTATION(EventType::subDomain, Vector2f) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + Vector2f v; + + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const PointerEvent* pe = + dynamic_cast(e->event)) + { + v[0] = pe->w(); + v[1] = pe->h(); + } + else if (const RenderEvent* re = + dynamic_cast(e->event)) + { + v[0] = re->w(); + v[1] = re->h(); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } + + if (e->event->eventTable()) + { + v[0] = e->event->eventTable()->bbox().size().x; + v[1] = e->event->eventTable()->bbox().size().y; + } + + return v; +} + +NODE_IMPLEMENTATION(EventType::domainVerticalFlip, bool) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + Vector2f v; + + if (e) + { + if (const RenderEvent* re = dynamic_cast(e->event)) + { + if (re->device()) + { + NODE_RETURN(re->device()->capabilities() & VideoDevice::FlippedImage); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "RenderEvent has unset device"); + } + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "event is not a RenderEvent"); + } + } + + NODE_RETURN(false); +} + + +NODE_IMPLEMENTATION(EventType::buttons, int) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + int b; + + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const PointerEvent* pe = + dynamic_cast(e->event)) + { + b = pe->buttonStates(); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } + + return b; +} + +NODE_IMPLEMENTATION(EventType::modifiers, int) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + int b; + + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const ModifierEvent* pe = + dynamic_cast(e->event)) + { + b = pe->modifiers(); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } + + return b; +} + +NODE_IMPLEMENTATION(EventType::timeStamp, float) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + NODE_RETURN(float(e->event->timeStamp())); +} + +NODE_IMPLEMENTATION(EventType::reject, void) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + e->event->handled = false; +} + +NODE_IMPLEMENTATION(EventType::setReturnContent, void) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + String* s = NODE_ARG_OBJECT(1, String); + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const GenericStringEvent* se = + dynamic_cast(e->event)) + { + se->setReturnContent(s->c_str()); + } + else + if (const RawDataEvent* rde = + dynamic_cast(e->event)) + { + rde->setReturnContent(s->c_str()); + } + else + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "bad event type"); + } +} + +NODE_IMPLEMENTATION(EventType::pressure, float) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const TabletEvent* te = + dynamic_cast(e->event)) + { + NODE_RETURN(float(te->pressure())); + } + else + { + NODE_RETURN(1.0f); + } +} + +NODE_IMPLEMENTATION(EventType::tangentialPressure, float) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const TabletEvent* te = + dynamic_cast(e->event)) + { + NODE_RETURN(float(te->tangentialPressure())); + } + else + { + NODE_RETURN(1.0f); + } +} + +NODE_IMPLEMENTATION(EventType::rotation, float) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const TabletEvent* te = + dynamic_cast(e->event)) + { + NODE_RETURN(float(te->rotation())); + } + else + { + NODE_RETURN(0.0f); + } +} + +NODE_IMPLEMENTATION(EventType::xTilt, int) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const TabletEvent* te = + dynamic_cast(e->event)) + { + NODE_RETURN(int(te->xTilt())); + } + else + { + NODE_RETURN(int(0)); + } +} + +NODE_IMPLEMENTATION(EventType::yTilt, int) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const TabletEvent* te = + dynamic_cast(e->event)) + { + NODE_RETURN(int(te->yTilt())); + } + else + { + NODE_RETURN(int(0)); + } +} + +NODE_IMPLEMENTATION(EventType::activationTime, float) +{ + EventInstance* e = NODE_ARG_OBJECT(0, EventInstance); + if (!e) + { + throwBadArgumentException(NODE_THIS, NODE_THREAD, "nil argument"); + } + + if (const PointerButtonPressEvent* pe = + dynamic_cast(e->event)) + { + NODE_RETURN(float(pe->activationTime())); + } + else + { + NODE_RETURN(0.0f); + } +} + + +} // namespace TwkApp diff --git a/src/lib/app/MuTwkApp/FunctionAction.cpp b/src/lib/app/MuTwkApp/FunctionAction.cpp new file mode 100644 index 000000000..8fe4e736c --- /dev/null +++ b/src/lib/app/MuTwkApp/FunctionAction.cpp @@ -0,0 +1,117 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace TwkApp { +using namespace std; + +MuFuncAction::MuFuncAction(Mu::FunctionObject* obj) + : Action() +{ + m_exception = false; + m_func = obj; + m_func->retainExternal(); +} + +MuFuncAction::MuFuncAction(Mu::FunctionObject* obj, const string& doc) + : Action(doc) +{ + m_func = obj; + m_exception = false; + m_func->retainExternal(); +} + +MuFuncAction::~MuFuncAction() +{ + m_func->releaseExternal(); +} + +void +MuFuncAction::execute(Document* d, const Event& event) const +{ + HOP_PROF_FUNC(); + + const EventType* etype = static_cast + (m_func->function()->argType(0)); + Mu::Process* p = muProcess(); + + EventType::EventInstance* e = + new EventType::EventInstance(etype); + e->event = &event; + event.handled = true; // the user can call reject() + e->document = d; + + Mu::Function::ArgumentVector args(1); + args.back() = Mu::Value(e); + + Mu::FunctionObject* preservedFunc = m_func; + ostringstream preservedFuncName; + m_func->function()->output(preservedFuncName); + + muAppThread()->call(m_func->function(), args, false); + + if (muAppThread()->uncaughtException() && !m_exception) + { + cerr << "ERROR: event = " + << event.name() + << endl; + + if (m_func != preservedFunc || !m_func || !(m_func->function())) + { + cerr << "ERROR: event handler function object corrupted!" << endl; + } + + cerr << "ERROR: function = " << preservedFuncName.str() << endl; + + if (const Mu::Object* o = muAppThread()->exception()) + { + cerr << "ERROR: Exception Value: "; + o->type()->outputValue(cerr, (Mu::ValuePointer)&o); + cerr << endl; + + if (o->type() == muContext()->exceptionType() && + muContext()->debugging()) + { + const Mu::ExceptionType::Exception* exc = + static_cast(o); + + cerr << "Backtrace:" << endl << exc->backtraceAsString() << endl; + } + } + + m_exception = true; + } + else + { + //m_exception = false; + } +} + +bool +MuFuncAction::error() const +{ + return m_exception; +} + +Action* +MuFuncAction::copy() const +{ + return new MuFuncAction(m_func, docString()); +} + +} // TwkApp diff --git a/src/lib/app/MuTwkApp/MenuItem.cpp b/src/lib/app/MuTwkApp/MenuItem.cpp new file mode 100644 index 000000000..506a9e883 --- /dev/null +++ b/src/lib/app/MuTwkApp/MenuItem.cpp @@ -0,0 +1,160 @@ +//****************************************************************************** +// Copyright (c) 2003 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace TwkApp { +using namespace Mu; +using namespace std; + +//---------------------------------------------------------------------- + + +MenuItem::MenuItem(Context* c, const char* name, Class *super) : Class(c, name, super) +{ +} + +MenuItem::~MenuItem() {} + +void +MenuItem::load() +{ + USING_MU_FUNCTION_SYMBOLS; + + Symbol *s = scope(); + MuLangContext* context = (MuLangContext*)globalModule()->context(); + Context* c = context; + + const char* className = "MenuItem"; + const char* refName = "MenuItem&"; + + string tname = s->name().c_str(); + tname += "."; + tname += className; + //string tname = "widget"; + + string rname = tname + "&"; + + const char* tn = tname.c_str(); + const char* rn = rname.c_str(); + + const char* mi = "MenuItem[]"; + const char* ft = "(void;Event)"; + const char* bft = "(int;)"; + + s->addSymbols( new ReferenceType(c, refName, this), + + new Function(c, className, BaseFunctions::dereference, Cast, + Return, tn, + Args, rn, End), + + EndArguments); + + globalScope()->addSymbols( + + new Function(c, "=", BaseFunctions::assign, AsOp, + Return, rn, + Args, rn, tn, End), + + EndArguments); + + // + // Layout of struct + // + + context->functionType(ft); + context->functionType(bft); + context->arrayType(this, 1, 0); + + addSymbols( new MemberVariable(c, "label", "string"), + new MemberVariable(c, "actionHook", ft), + new MemberVariable(c, "key", "string"), + new MemberVariable(c, "stateHook", bft), + new MemberVariable(c, "subMenu", mi), + + new Function(c, "__allocate", BaseFunctions::classAllocate, Function::None, + Function::Return, tn, + Function::End), + + new Function(c, className, MenuItem::construct, None, + Return, tn, + Parameters, + new ParameterVariable(c, "this", tn), + new ParameterVariable(c, "label", "string"), + new ParameterVariable(c, "actionHook", ft), + new ParameterVariable(c, "key", "string", Value(Pointer(0))), + new ParameterVariable(c, "stateHook", bft, Value(Pointer(0))), + new ParameterVariable(c, "subMenu", mi, Value(Pointer(0))), + End), + + new Function(c, className, MenuItem::construct2, None, + Return, tn, + Parameters, + new ParameterVariable(c, "this", tn), + new ParameterVariable(c, "label", "string"), + new ParameterVariable(c, "subMenu", mi), + End), + + EndArguments ); + +} + + +NODE_IMPLEMENTATION(MenuItem::construct, Pointer) +{ + Process *p = NODE_THREAD.process(); + const Class *c = static_cast(NODE_THIS.type()); + + ClassInstance *o = NODE_ARG_OBJECT(0, ClassInstance); + StringType::String* label = NODE_ARG_OBJECT(1, StringType::String); + FunctionObject* fobj = NODE_ARG_OBJECT(2, FunctionObject); + StringType::String* key = NODE_ARG_OBJECT(3, StringType::String); + FunctionObject* sobj = NODE_ARG_OBJECT(4, FunctionObject); + DynamicArray* array = NODE_ARG_OBJECT(5, DynamicArray); + + Struct* s = o->data(); + + s->label = label; + s->key = key; + s->actionCB = fobj; + s->stateCB = sobj; + s->subMenu = array; + + NODE_RETURN(Pointer(o)); +} + +NODE_IMPLEMENTATION(MenuItem::construct2, Pointer) +{ + Process *p = NODE_THREAD.process(); + const Class *c = static_cast(NODE_THIS.type()); + + ClassInstance *o = NODE_ARG_OBJECT(0, ClassInstance); + StringType::String* label = NODE_ARG_OBJECT(1, StringType::String); + DynamicArray* array = NODE_ARG_OBJECT(2, DynamicArray); + Struct* s = o->data(); + + s->label = label; + s->key = 0; + s->actionCB = 0; + s->stateCB = 0; + s->subMenu = array; + + NODE_RETURN(Pointer(o)); +} + +} // TwkApp diff --git a/src/lib/app/MuTwkApp/MenuState.cpp b/src/lib/app/MuTwkApp/MenuState.cpp new file mode 100644 index 000000000..964ff4805 --- /dev/null +++ b/src/lib/app/MuTwkApp/MenuState.cpp @@ -0,0 +1,86 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace TwkApp { +using namespace std; + +MuStateFunc::MuStateFunc(Mu::FunctionObject* obj) : m_func(obj), m_exception(false) +{ + m_func->retainExternal(); +} + +MuStateFunc::~MuStateFunc() +{ + m_func->releaseExternal(); + m_func = 0; +} + +int +MuStateFunc::state() +{ + assert(m_func); + Mu::Process* p = muProcess(); + Mu::Function::ArgumentVector args; + const Mu::Function* F = m_func->function(); + Value v = muAppThread()->call(F, args, false); + + if (muAppThread()->uncaughtException() && !m_exception) + { + cerr << "ERROR: while evaluating function: "; + Value v(m_func); + m_func->type()->outputValue(cerr, v); + cerr << endl; + + if (const Mu::Object* e = muAppThread()->exception()) + { + cerr << "ERROR: Exception Value: "; + e->type()->outputValue(cerr, (ValuePointer)&e); + cerr << endl; + + if (e->type() == muContext()->exceptionType() && + muContext()->debugging()) + { + const Mu::ExceptionType::Exception* exc = + static_cast(e); + + cerr << "Backtrace:" << endl << exc->backtraceAsString() << endl; + } + } + + m_exception = true; + } + else + { + //m_exception = false; + } + + return v._int; +} + +bool +MuStateFunc::error() const +{ + return m_exception; +} + +Menu::StateFunc* +MuStateFunc::copy() const +{ + return new MuStateFunc(m_func); +} + +} // TwkApp diff --git a/src/lib/app/MuTwkApp/MuInterface.cpp b/src/lib/app/MuTwkApp/MuInterface.cpp new file mode 100644 index 000000000..c848d3253 --- /dev/null +++ b/src/lib/app/MuTwkApp/MuInterface.cpp @@ -0,0 +1,577 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#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 + +using namespace Mu; +using namespace std; +using namespace TwkUtil; + +// +// This will intialize the garbage collector among other things. +// + +GenericMachine* g_machine = 0; + +// +// For complete saftey (because we don't know the order in which +// static initialization occurs), these are allocated just before +// first use. We don't want to call an GC functions before its +// initialized. +// + +static Context::ModuleList g_modules; +static MuLangContext* g_context = 0; +static MuLangLanguage* g_language = 0; +static Process* g_process = 0; +static Thread* g_appThread = 0; + +// +// This should to be a container searchable by the GC +// + +struct ThreadMapEntry +{ + pthread_t posixThread; + Thread* muThread; +}; + +typedef Mu::STLVector::Type ThreadMap; +static ThreadMap threadmap; + +extern const char *mu_commands; + +namespace TwkApp { + +static bool debugging = false; + +CallEnv::CallEnv(Document* d) : Mu::CallEnvironment(), _doc(d) +{ +} + +CallEnv::~CallEnv() +{ +} + +const Value +CallEnv::call(const Function* F, Function::ArgumentVector& args) const +{ + if (_doc) + { + Document* current = Document::activeDocument(); + if (current != _doc) _doc->makeActive(); + const Value v = muProcess()->call(muAppThread(), F, args); + if (current != _doc) current->makeActive(); + return v; + } + else + { + return Value(Pointer(0)); + } +} + +const Value +CallEnv::callMethodByName(const char* Fname, Function::ArgumentVector& args) const +{ + if (_doc) + { + Document* current = Document::activeDocument(); + if (current != _doc) _doc->makeActive(); + const Value v = muAppThread()->callMethodByName(Fname, args, false); + if (current != _doc) current->makeActive(); + return v; + } + else + { + return Value(Pointer(0)); + } +} + +const Context* +CallEnv::context() const +{ + return muContext(); +} + +//---------------------------------------------------------------------- + +void +setDebugging(bool b) +{ + debugging = true; + if (g_context) g_context->debugging(true); +} + +void +setCompileOnDemand(bool b) +{ + Module::setCompileOnDemand(b, b); // sets both mud and muc output +} + +void +setDebugMUC(bool b) +{ + Module::setDebugArchive(b); +} + +bool +isDebuggingOn() +{ + return g_context ? g_context->debugging() : debugging; +} + +Context::ModuleList& +muModuleList() +{ + return g_modules; +} + +Mu::MuLangContext* +muContext() +{ + return g_context; +} + +Mu::Process* +muProcess() +{ + return g_process; +} + +Mu::Thread* +muAppThread() +{ + for (int i=0; i < threadmap.size(); i++) + { + if (pthread_equal(threadmap[i].posixThread, pthread_self())) + { + return threadmap[i].muThread; + } + } + + ThreadMapEntry entry; + entry.posixThread = pthread_self(); + entry.muThread = muProcess()->newApplicationThread(); + threadmap.push_back(entry); + return entry.muThread; +} + +// alternative to readline + +const char* +getline(const char *prompt) +{ + static char buffer[1024]; + size_t max_size = 1024; + + cout << prompt << flush; + + int i = 0; + + for (;i < max_size; i++) + { + if (!cin.eof() && !cin.fail()) + { + char c; + cin.get(c); + if (c == '\n') + { + buffer[i] = 0; + return buffer; + } + else + { + buffer[i] = c; + } + } + else + { + break; + } + } + + if (i) + { + if (i != max_size) i--; + return buffer; + } + else + { + return 0; + } +} + + +//---------------------------------------------------------------------- + +std::string +muEval(MuLangContext* context, + Process* process, + const Context::ModuleList& modules, + const char* line, + const char* contextName, + bool showType) +{ + ostringstream str; + + if (*line) + { + try + { + Mu::TypedValue value = context->evalText(line, + contextName, + process, + modules); + + if (value._type && value._type != context->voidType()) + { + if (showType) + { + value._type->output(str); + str << " => "; + } + + value._type->outputValue(str, value._value); + } + } + catch (Mu::TypedValue value) + { + if (Mu::ExceptionType::Exception* e = + (Mu::ExceptionType::Exception*) value._value._Pointer) + { + str << "ERROR: " << e->string() << endl; + cerr << e->backtraceAsString() << endl; + } + } + catch (std::exception& e) + { + cout << "ERROR: uncaught exception: " << e.what() << endl; + } + } + + return str.str(); +} + +std::string +muEvalStringExpr(MuLangContext* context, + Process* process, + const Context::ModuleList& modules, + const char* line, + const char* contextName) +{ + ostringstream str; + + if (*line) + { + try + { + Mu::TypedValue value = context->evalText(line, + contextName, + process, + modules); + + if (value._type && value._type == context->stringType()) + { + StringType::String* s = reinterpret_cast(value._value._Pointer); + str << s->c_str(); + } + } + catch (Mu::TypedValue value) + { + if (Mu::ExceptionType::Exception* e = + (Mu::ExceptionType::Exception*) value._value._Pointer) + { + str << "ERROR: " << e->string() << endl; + cerr << e->backtraceAsString() << endl; + } + } + catch (std::exception& e) + { + cout << "ERROR: uncaught exception: " << e.what() << endl; + } + } + + return str.str(); +} + +void +cli() +{ + cout << "Type `help()' for a list of commands." << endl; + cout << "or `help(\"name of command\")' for help on a specific command." << endl; + + while (1) + { + const char *line; + line = getline("rv> "); + + if (!line) exit(0); + if (!*line) continue; + + //add_history(line); + + string command = line; + command += ";"; + + try + { + Mu::TypedValue value = g_context->evalText(command.c_str(), "input", + g_process, + g_modules); + + if (value._type && value._type != g_context->voidType()) + { + value._type->output(cout); + cout << " => "; + value._type->outputValue(cout, value._value); + cout << endl << flush; + } + } + catch (Mu::TypedValue value) + { + if (Mu::ExceptionType::Exception* e = + (Mu::ExceptionType::Exception*) value._value._Pointer) + { + cout << "ERROR: " << e->string() << endl; + cerr << e->backtraceAsString() << endl; + } + } + catch (std::exception& e) + { + cout << "ERROR: uncaught exception: " << e.what() << endl; + } + } +} + +void +batch(MuLangContext* context, + Process* process, + const Context::ModuleList& modules, + const char *filename) +{ + NodeAssembler as(context, process); + + for (int i=0; i < modules.size(); i++) + { + as.pushScope((Module*)modules[i], false); + } + + ifstream in(UNICODE_C_STR(filename)); + context->setInput(in); + + if (Process *p = Parse(filename, &as)) + { + if (p->rootNode()) + { + Thread* thread = muAppThread(); + Value v = p->evaluate(thread); + + if (thread->uncaughtException()) + { + if (Mu::ExceptionType::Exception* e = + (Mu::ExceptionType::Exception*) thread->exception()) + + { + cerr << "ERROR: " << e->string() << endl; + cerr << e->backtraceAsString() << endl; + } + + exit(-1); + } + } + } + else + { + exit(-1); + } +} + +MuLangContext* +newMuContext(const char* batchFile, GCFilterFunc gc_filter, Context::ModuleList& modules) +{ +#ifdef PLATFORM_DARWIN + GarbageCollector::init(); + if (gc_filter != 0) GC_register_has_static_roots_callback(gc_filter); +#endif + + if (!g_machine) g_machine = new GenericMachine(); + if (!g_language) g_language = new MuLangLanguage; + + MuLangContext* context = new MuLangContext( batchFile ? "batch" : "cli", + batchFile ? batchFile : "input" ); + + context->debugging(debugging); + + // Module* autodoc = new Mu::AutoDocModule(context, "autodoc"); + // Module* io = new Mu::IOModule(context, "io"); + // Module* sys = new Mu::SystemModule(context, "system"); + // Module* img = new Mu::ImageModule(context, "image"); + // Module* enc = new Mu::EncodingModule(context, "encoding"); + // Module* lin = new Mu::MathLinearModule(context, "math_linear"); + Module* commands = new CommandsModule(context, "commands"); + + context->globalScope()->addSymbol(new EventType(context)); + context->globalScope()->addSymbol(new SettingsValueType(context)); + // context->globalScope()->addSymbol(autodoc); + // context->globalScope()->addSymbol(io); + // context->globalScope()->addSymbol(sys); + // context->globalScope()->addSymbol(img); + // context->globalScope()->addSymbol(enc); + // context->globalScope()->addSymbol(lin); + context->globalScope()->addSymbol(commands); + + modules.push_back(commands); + //modules.push_back(autodoc); + //modules.push_back(io); + modules.push_back(context->mathModule()); + modules.push_back(context->mathUtilModule()); + + return context; +} + +void +initMu(const char* batchFile, GCFilterFunc gc_filter) +{ + if (!g_context) + { + g_context = newMuContext(batchFile, gc_filter, g_modules); + } + + if (!g_process) + { + g_process = new Process(g_context); + } + + if (!g_appThread) + { + g_appThread = muAppThread(); + } +} + +void +initWithString(MuLangContext* context, + Process* process, + const Context::ModuleList& modules, + const char* p) +{ + try + { + //Timer t; + context->evalText(p, "initWithString", process, modules); + //cout << "EVAL: " << t.elapsed() << " seconds" << endl; + } + catch (StreamOpenFailureException) + { + // Not an error if no file exists + } +} + +void +initWithFile(MuLangContext* context, + Process* process, + const Context::ModuleList& modules, + const char* file) +{ + try + { + //Timer t; + context->evalFile(file, process, modules); + //cout << "EVAL: " << t.elapsed() << " seconds" << endl; + } + catch (StreamOpenFailureException) + { + // Not an error if no file exists + } +} + +void +initRc(MuLangContext* context, + Process* process, + const Context::ModuleList& modules, + const char* rcfile) +{ + ostringstream str; + str << getenv("HOME") << rcfile; + initWithFile(context, process, modules, str.str().c_str()); +} + +Menu* +createTwkAppMenu(const std::string& name, DynamicArray* array) +{ + size_t size = array->size(); + Menu* menu = new Menu(name); + + for (int q=0; q < size; q++) + { + if (ClassInstance* i = array->element(q)) + { + MenuItem::Struct* s = i->data(); + Menu* subMenu = 0; + + if (s->subMenu) + { + subMenu = createTwkAppMenu(s->label->c_str(), s->subMenu); + } + + MuFuncAction* action = s->actionCB ? new MuFuncAction(s->actionCB) : 0; + MuStateFunc* sfunc = s->stateCB ? new MuStateFunc(s->stateCB) : 0; + + menu->addItem(new Menu::Item(s->label->c_str(), + action, + s->key ? s->key->c_str() : "", + sfunc, + subMenu)); + } + } + + return menu; +} + +void +runMuInterative() +{ + InteractiveSession session; + session.run(muContext(), muProcess(), muAppThread()); +} + + +} diff --git a/src/lib/app/MuTwkApp/MuTwkApp/CommandsModule.h b/src/lib/app/MuTwkApp/MuTwkApp/CommandsModule.h new file mode 100644 index 000000000..e928217c1 --- /dev/null +++ b/src/lib/app/MuTwkApp/MuTwkApp/CommandsModule.h @@ -0,0 +1,66 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifndef __MuTwkApp__CommandsModule__h__ +#define __MuTwkApp__CommandsModule__h__ +#include +#include +#include + +namespace TwkApp { + +class CommandsModule : public Mu::Module +{ +public: + CommandsModule(Mu::Context* c, const char *name); + virtual ~CommandsModule(); + + virtual void load(); + + static NODE_DECLARATION(eval, Mu::Pointer); + + static NODE_DECLARATION(undo, void); + static NODE_DECLARATION(redo, void); + static NODE_DECLARATION(clearHistory, void); + static NODE_DECLARATION(undoHistory, Mu::Pointer); + static NODE_DECLARATION(redoHistory, Mu::Pointer); + static NODE_DECLARATION(beginCompoundCommand, void); + static NODE_DECLARATION(endCompoundCommand, void); + static NODE_DECLARATION(readFile, void); + static NODE_DECLARATION(writeFile, void); + static NODE_DECLARATION(defineMinorMode, void); + static NODE_DECLARATION(activateMode, void); + static NODE_DECLARATION(isModeActive, bool); + static NODE_DECLARATION(deactivateMode, void); + static NODE_DECLARATION(bindDoc, Mu::Pointer); + static NODE_DECLARATION(bindings, Mu::Pointer); + static NODE_DECLARATION(bind, void); + static NODE_DECLARATION(bindRegex, void); + static NODE_DECLARATION(unbind, void); + static NODE_DECLARATION(unbindRegex, void); + static NODE_DECLARATION(setTableBBox, void); + static NODE_DECLARATION(pushEventTable, void); + static NODE_DECLARATION(popEventTable, void); + static NODE_DECLARATION(popNamedEventTable, void); + static NODE_DECLARATION(activeEventTables, Mu::Pointer); + + static NODE_DECLARATION(defineModeMenu, void); + // static NODE_DECLARATION(isComputationInProgress, bool); + // static NODE_DECLARATION(computationMessage, Mu::Pointer); + // static NODE_DECLARATION(computationProgress, float); + // static NODE_DECLARATION(computationElapsedTime, float); + static NODE_DECLARATION(contractSeq, Mu::Pointer); + + static NODE_DECLARATION(activeModes, Mu::Pointer); + static NODE_DECLARATION(sequenceOfFile, Mu::Pointer); +}; + + + +} // TwkApp + +#endif // __MuTwkApp__CommandsModule__h__ diff --git a/src/lib/app/MuTwkApp/MuTwkApp/EventType.h b/src/lib/app/MuTwkApp/MuTwkApp/EventType.h new file mode 100644 index 000000000..eaee7f5b4 --- /dev/null +++ b/src/lib/app/MuTwkApp/MuTwkApp/EventType.h @@ -0,0 +1,82 @@ +//****************************************************************************** +// Copyright (c) 2004 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifndef __TwkApp__EventType__h__ +#define __TwkApp__EventType__h__ +#include +#include +#include +#include + +namespace TwkApp { +class Document; +class Event; + +class EventType : public Mu::Class +{ + public: + EventType(Mu::Context*, Mu::Class *super=0); + ~EventType(); + + class EventInstance : public Mu::ClassInstance + { + public: + EventInstance(const Mu::Class*); + + const Event* event; + const Document* document; + protected: + friend class EventType; + }; + + // + // API + // + + virtual Mu::Object* newObject() const; + virtual void deleteObject(Mu::Object*) const; + virtual void outputValue(std::ostream&, const Mu::Value&, bool full=false) const; + virtual void outputValueRecursive(std::ostream&, + const Mu::ValuePointer, + Mu::Type::ValueOutputState&) const; + virtual void load(); + + static NODE_DECLARATION(print, void); + static NODE_DECLARATION(pointer, Mu::Vector2f); + static NODE_DECLARATION(relativePointer, Mu::Vector2f); + static NODE_DECLARATION(reference, Mu::Vector2f); + static NODE_DECLARATION(domainVerticalFlip, bool); + static NODE_DECLARATION(domain, Mu::Vector2f); + static NODE_DECLARATION(subDomain, Mu::Vector2f); + static NODE_DECLARATION(buttons, int); + static NODE_DECLARATION(modifiers, int); + static NODE_DECLARATION(name, Mu::Pointer); + static NODE_DECLARATION(key, int); + static NODE_DECLARATION(dataContents, Mu::Pointer); + static NODE_DECLARATION(interp, Mu::Pointer); + static NODE_DECLARATION(contents, Mu::Pointer); + static NODE_DECLARATION(contentsArray, Mu::Pointer); + static NODE_DECLARATION(returnContents, Mu::Pointer); + static NODE_DECLARATION(contentType, int); + static NODE_DECLARATION(contentMimeType, Mu::Pointer); + static NODE_DECLARATION(timeStamp, float); + static NODE_DECLARATION(reject, void); + static NODE_DECLARATION(setReturnContent, void); + static NODE_DECLARATION(sender, Mu::Pointer); + static NODE_DECLARATION(pressure, float); + static NODE_DECLARATION(tangentialPressure, float); + static NODE_DECLARATION(rotation, float); + static NODE_DECLARATION(xTilt, int); + static NODE_DECLARATION(yTilt, int); + static NODE_DECLARATION(activationTime, float); + +private: +}; + +} // TwkApp + +#endif // __TwkApp__EventType__h__ diff --git a/src/lib/app/MuTwkApp/MuTwkApp/FunctionAction.h b/src/lib/app/MuTwkApp/MuTwkApp/FunctionAction.h new file mode 100644 index 000000000..8abb0238b --- /dev/null +++ b/src/lib/app/MuTwkApp/MuTwkApp/FunctionAction.h @@ -0,0 +1,38 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifndef __MuTwkApp__FunctionAction__h__ +#define __MuTwkApp__FunctionAction__h__ +#include +#include + +namespace TwkApp { + +// +// An action which holds a function object and executes on demand. +// + +class MuFuncAction : public Action +{ + public: + MuFuncAction(Mu::FunctionObject*); + MuFuncAction(Mu::FunctionObject*, const std::string& docstring); + virtual ~MuFuncAction(); + virtual void execute(Document*, const Event&) const; + virtual Action* copy() const; + virtual bool error() const; + + Mu::FunctionObject* fobj() const { return m_func; } + + private: + Mu::FunctionObject* m_func; + mutable bool m_exception; +}; + +} // TwkApp + +#endif // __MuTwkApp__FunctionAction__h__ diff --git a/src/lib/app/MuTwkApp/MuTwkApp/MenuItem.h b/src/lib/app/MuTwkApp/MuTwkApp/MenuItem.h new file mode 100644 index 000000000..791c4f208 --- /dev/null +++ b/src/lib/app/MuTwkApp/MuTwkApp/MenuItem.h @@ -0,0 +1,63 @@ +//****************************************************************************** +// Copyright (c) 2003 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifndef __MuTwkApp__MenuItem__h__ +#define __MuTwkApp__MenuItem__h__ +#include +#include +#include +#include +#include +#include + +namespace TwkApp { +using namespace Mu; + +// +// MenuItem is the base of class hierarchy for UI. +// + +class MenuItem : public Class +{ +public: + // + // ClassInstance Structure + // + + struct Struct + { + StringType::String* label; + FunctionObject* actionCB; + StringType::String* key; + FunctionObject* stateCB; + DynamicArray* subMenu; + }; + + // + // Constructors + // + + MenuItem(Context* c, const char* name, Class *super=0); + ~MenuItem(); + + // + // Symbol API + // + + virtual void load(); + + // + // Constant + // + + static NODE_DECLARATION(construct, Pointer); + static NODE_DECLARATION(construct2, Pointer); +}; + +} // TwkApp + +#endif // __MuTwkApp__MenuItem__h__ diff --git a/src/lib/app/MuTwkApp/MuTwkApp/MenuState.h b/src/lib/app/MuTwkApp/MuTwkApp/MenuState.h new file mode 100644 index 000000000..8d9c44404 --- /dev/null +++ b/src/lib/app/MuTwkApp/MuTwkApp/MenuState.h @@ -0,0 +1,34 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifndef __MuTwkApp__MenuState__h__ +#define __MuTwkApp__MenuState__h__ +#include +#include + +namespace TwkApp { + +class MuStateFunc : public Menu::StateFunc +{ +public: + MuStateFunc(Mu::FunctionObject*); + virtual ~MuStateFunc(); + virtual int state(); + virtual Menu::StateFunc* copy() const; + virtual bool error() const; + + bool exceptionOccuredLastTime() const { return m_exception; } + +private: + Mu::FunctionObject* m_func; + bool m_exception; +}; + + +} // TwkApp + +#endif // __MuTwkApp__MenuState__h__ diff --git a/src/lib/app/MuTwkApp/MuTwkApp/MuInterface.h b/src/lib/app/MuTwkApp/MuTwkApp/MuInterface.h new file mode 100644 index 000000000..0e3a18ec1 --- /dev/null +++ b/src/lib/app/MuTwkApp/MuTwkApp/MuInterface.h @@ -0,0 +1,94 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifndef __MuTwkApp__MuInterface__h__ +#define __MuTwkApp__MuInterface__h__ +#include +#include +#include + +namespace TwkApp { +class Menu; +class Document; + +class CallEnv : public Mu::CallEnvironment +{ + public: + CallEnv(Document* d); + virtual ~CallEnv(); + virtual const Mu::Value call(const Mu::Function*, Mu::Function::ArgumentVector&) const; + virtual const Mu::Value callMethodByName(const char*, Mu::Function::ArgumentVector&) const; + virtual const Mu::Context* context() const; + void invalidate() { _doc = 0; } + private: + Document* _doc; +}; + +Mu::Context::ModuleList& muModuleList(); +Mu::MuLangContext* muContext(); +Mu::Process* muProcess(); +Mu::Thread* muAppThread(); + +typedef int (*GCFilterFunc)(const char*, void*, size_t); + +void setDebugging(bool); +void setCompileOnDemand(bool); +void setDebugMUC(bool); +bool isDebuggingOn(); + +std::string muEval(Mu::MuLangContext*, + Mu::Process*, + const Mu::Context::ModuleList& modules, + const char* line, + const char* contextName="eval", + bool showType = true); + +std::string muEvalStringExpr(Mu::MuLangContext*, + Mu::Process*, + const Mu::Context::ModuleList& modules, + const char* line, + const char* contextName="eval"); +void cli(); + +void batch(Mu::MuLangContext*, + Mu::Process*, + const Mu::Context::ModuleList&, + const char* filename); + +void initMu(const char* batchfile, GCFilterFunc F = 0); + +void initWithFile(Mu::MuLangContext*, + Mu::Process*, + const Mu::Context::ModuleList&, + const char* filename); + +void initWithString(Mu::MuLangContext*, + Mu::Process*, + const Mu::Context::ModuleList&, + const char* buffer); + +void initRc(Mu::MuLangContext*, + Mu::Process*, + const Mu::Context::ModuleList&, + const char* rcfile); + +TwkApp::Menu* createTwkAppMenu(const std::string& name, Mu::DynamicArray* array); + +// +// For multi-threaded, etc, you can create a unique app context for +// each thread by calling this. batchfile and gc_filter can be 0 +// + +Mu::MuLangContext* newMuContext(const char* batchFile, + GCFilterFunc gc_filter, + Mu::Context::ModuleList& modules); + +void runMuInterative(); + +} + +#endif // __MuTwkApp__MuInterface__h__ diff --git a/src/lib/app/MuTwkApp/MuTwkApp/SettingsValueType.h b/src/lib/app/MuTwkApp/MuTwkApp/SettingsValueType.h new file mode 100644 index 000000000..5d3ff59b7 --- /dev/null +++ b/src/lib/app/MuTwkApp/MuTwkApp/SettingsValueType.h @@ -0,0 +1,65 @@ +// +// Copyright (c) 2009 Tweak Software. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +#ifndef __TwkApp__SettingsValueType__h__ +#define __TwkApp__SettingsValueType__h__ +#include +#include + +namespace Mu { +class VariantInstance; +} + +namespace TwkApp { + + +class SettingsValueType : public Mu::VariantType +{ + public: + + enum ValueType + { + NoType, + FloatType, + IntType, + BoolType, + StringType, + StringArrayType, + FloatArrayType, + IntArrayType + }; + + SettingsValueType(Mu::Context*); + virtual ~SettingsValueType(); + + virtual void load(); + + static NODE_DECLARATION(defaultConstructor, Mu::Pointer); + + ValueType valueType(Mu::VariantInstance*) const; + + const Mu::VariantTagType* boolType() const { return _boolType; } + const Mu::VariantTagType* intType() const { return _intType; } + const Mu::VariantTagType* floatType() const { return _floatType; } + const Mu::VariantTagType* stringType() const { return _stringType; } + const Mu::VariantTagType* stringArrayType() const { return _stringArrayType; } + const Mu::VariantTagType* floatArrayType() const { return _floatArrayType; } + const Mu::VariantTagType* intArrayType() const { return _intArrayType; } + + private: + Mu::VariantTagType* _boolType; + Mu::VariantTagType* _intType; + Mu::VariantTagType* _floatType; + Mu::VariantTagType* _stringType; + Mu::VariantTagType* _intArrayType; + Mu::VariantTagType* _stringArrayType; + Mu::VariantTagType* _floatArrayType; +}; + +} // TwkApp + +#endif // __TwkApp__SettingsValueType__h__ diff --git a/src/lib/app/MuTwkApp/SettingsValueType.cpp b/src/lib/app/MuTwkApp/SettingsValueType.cpp new file mode 100644 index 000000000..699c68b2a --- /dev/null +++ b/src/lib/app/MuTwkApp/SettingsValueType.cpp @@ -0,0 +1,113 @@ +// +// Copyright (c) 2009 Tweak Software. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace TwkApp { +using namespace std; +using namespace Mu; + +SettingsValueType::SettingsValueType(Context* c) : VariantType(c, "SettingsValue") {} +SettingsValueType::~SettingsValueType() {} + +void +SettingsValueType::load() +{ + USING_MU_FUNCTION_SYMBOLS; + + MuLangContext* context = (MuLangContext*)globalModule()->context(); + Context* c = context; + context->arrayType(context->floatType(), 1, 0); + context->arrayType(context->intType(), 1, 0); + context->arrayType(context->stringType(), 1, 0); + + _floatType = new VariantTagType(c, "Float", "float"); + _intType = new VariantTagType(c, "Int", "int"); + _stringType = new VariantTagType(c, "String", "string"); + _boolType = new VariantTagType(c, "Bool", "bool"); + _floatArrayType = new VariantTagType(c, "FloatArray", "float[]"); + _intArrayType = new VariantTagType(c, "IntArray", "int[]"); + _stringArrayType = new VariantTagType(c, "StringArray", "string[]"); + + addSymbols(_floatType, _intType, _stringType, _boolType, + _floatArrayType, _intArrayType, _stringArrayType, + EndArguments); + + String rtname = name().c_str(); + rtname += "&"; + ReferenceType* rt = new ReferenceType(c, rtname.c_str(), this); + scope()->addSymbol(rt); + + const char* tn = fullyQualifiedName().c_str(); + +#if 0 + for (int i=0 i < constructors.size(); i++) + { + // + // Add default constructors (w/o arguments). + // + + VariantTagType* t = constructors[i]; + + t->addSymbol( new Function(c, t->name().c_str(), + SettingsValueType::defaultConstructor, None, + Return, tn, + End) ); + } +#endif + + Function* OpAs = new Function(c, "=", + BaseFunctions::assign, + Function::MemberOperator | Function::Operator, + Function::Return, + rt->fullyQualifiedName().c_str(), + Function::Args, + rt->fullyQualifiedName().c_str(), + tn, + Function::End); + + globalScope()->addSymbol(OpAs); +} + +NODE_IMPLEMENTATION(SettingsValueType::defaultConstructor, Pointer) +{ + // + // Tricky -- get the scope of the constructor symbol -- that's + // the tag type. + // + + const VariantTagType* t = + static_cast(NODE_THIS.symbol()->scope()); + + NODE_RETURN(VariantInstance::allocate(t)); +} + +SettingsValueType::ValueType +SettingsValueType::valueType(VariantInstance* vobj) const +{ + const VariantTagType* tt = static_cast(vobj->type()); + + if (tt == _stringType) return StringType; + else if (tt == _intType) return IntType; + else if (tt == _boolType) return BoolType; + else if (tt == _floatType) return FloatType; + else if (tt == _stringArrayType) return StringArrayType; + else if (tt == _floatArrayType) return FloatArrayType; + else if (tt == _intArrayType) return IntArrayType; + return NoType; +} + +} // TwkApp diff --git a/src/lib/app/OutputVideoDevices/CMakeLists.txt b/src/lib/app/OutputVideoDevices/CMakeLists.txt new file mode 100644 index 000000000..ada0c7533 --- /dev/null +++ b/src/lib/app/OutputVideoDevices/CMakeLists.txt @@ -0,0 +1,49 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "OutputVideoDevices" +) + +SET(_sources + OutputVideoDevice.cpp OutputVideoDeviceModule.cpp +) + +ADD_LIBRARY( + ${_target} STATIC + ${_sources} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PUBLIC TwkApp TwkGLF TwkUtil TwkMovie stl_ext Boost::thread + PRIVATE TwkAudio IPCore TwkExc +) + +IF(RV_TARGET_LINUX) + FIND_PACKAGE(OpenGL REQUIRED) + + TARGET_LINK_LIBRARIES( + ${_target} + PUBLIC OpenGL::GL OpenGL::GLU OpenGL::GLX + ) +ENDIF() + +IF(RV_TARGET_WINDOWS) + TARGET_LINK_LIBRARIES( + ${_target} + PUBLIC win_pthreads glew + ) +ENDIF() + +RV_STAGE(TYPE "LIBRARY" TARGET ${_target}) diff --git a/src/lib/app/OutputVideoDevices/OutputVideoDevice.cpp b/src/lib/app/OutputVideoDevices/OutputVideoDevice.cpp new file mode 100644 index 000000000..8b3e11c84 --- /dev/null +++ b/src/lib/app/OutputVideoDevices/OutputVideoDevice.cpp @@ -0,0 +1,1643 @@ +// +// Copyright (c) 2014 Tweak Software. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef PLATFORM_WINDOWS +#include +#include +#include +#define DEFAULT_RINGBUFFER_SIZE 4 +#endif + +#ifdef PLATFORM_LINUX +#include +#define DEFAULT_RINGBUFFER_SIZE 3 +#endif + +#ifdef PLATFORM_DARWIN +#define DEFAULT_RINGBUFFER_SIZE 4 +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // this needs to come after boost incls for vc12 + +#define GC_EXCLUDE_THIS_THREAD + +namespace OutputVideoDevices { +using namespace std; +using namespace TwkApp; +using namespace TwkGLF; +using namespace TwkUtil; +using namespace boost::program_options; +using namespace boost::algorithm; +using namespace boost; + +namespace { +bool m_infoFeedback = false; + +string +mapToEnvVar(string name) +{ + if (name == "TWK_OUTPUT_HELP") return "help"; + if (name == "TWK_OUTPUT_VERBOSE") return "verbose"; + if (name == "TWK_OUTPUT_PROFILE") return "profile"; + if (name == "TWK_OUTPUT_METHOD") return "method"; + if (name == "TWK_OUTPUT_RING_BUFFER_SIZE") return "ring-buffer-size"; + return ""; +} + +} + +// // +// // Movie API is used to generically read from the +// // OutputVideoDevice queues +// // + +// class MovieInterface : public TwkMovie::Movie +// { +// public: +// MovieInterface(OutputVideoDevice* d) : device(d) {} +// virtual ~MovieInterface() {} +// virtual void imagesAtFrame(const ReadRequest&, FrameBufferVector&); +// virtual size_t audioFillBuffer(const AudioReadRequest&, AudioBuffer&); +// OutputVideoDevice* device; +// }; + + +OutputVideoDevice::VideoChannel::VideoChannel(size_t bsize, size_t n) + : data(n), + bufferSizeInBytes(bsize) +{ +} + +OutputVideoDevice::VideoChannel::~VideoChannel() +{ +} + +OutputVideoDevice::FrameData::FrameData() + : fbo(0), + globject(0), + imageData(0), + state(NotReady), + mappedPointer(0), + fence(0), + locked(false) +{ +} + +OutputVideoDevice::FrameData::FrameData(const OutputVideoDevice::FrameData& f) + : fbo(f.fbo), + globject(f.globject), + imageData(f.imageData), + state(f.state), + mappedPointer(f.mappedPointer), + fence(f.fence), + locked(false) +{ +} + +OutputVideoDevice::FrameData& +OutputVideoDevice::FrameData::operator= (const OutputVideoDevice::FrameData& f) +{ + fbo = f.fbo; + globject = f.globject; + imageData = f.imageData; + state = f.state; + mappedPointer = f.mappedPointer; + fence = f.fence; + locked = false; + return *this; +} + + +OutputVideoDevice::FrameData::~FrameData() +{ +} + +void +OutputVideoDevice::FrameData::lockImage(const char* threadName) +{ + Timer timer; + timer.start(); + imageMutex.lock(); + Time t = timer.elapsed(); + if (t > 0.001 && m_infoFeedback) cerr << "INFO: " << threadName << ": lockImage for " << t << endl; +} + +void OutputVideoDevice::FrameData::unlockImage() +{ + imageMutex.unlock(); +} + +void +OutputVideoDevice::FrameData::lockState(const char* threadName) +{ + Timer timer; + timer.start(); + stateMutex.lock(); + Time t = timer.elapsed(); + if (t > 0.001 && m_infoFeedback) cerr << "INFO: " << threadName << ": lockState for " << t << endl; +} + +void +OutputVideoDevice::FrameData::unlockState() +{ + stateMutex.unlock(); +} + +OutputVideoDevice::OutputVideoDevice(OutputVideoDeviceModule* m, const string& name, IPCore::IPGraph* graph) + : GLBindableVideoDevice(m, name, + BlockingTransfer | + ASyncReadBack | + ImageOutput | + ProvidesSync | + FixedResolution | + NormalizedCoordinates | + Clock), // NOTE: no AudioOutput flag here on purpose + m_channels(3), + m_width(1920), + m_height(1080), + m_bits(8), + m_float(false), + m_fps(24.0f), + m_profile(false), + m_frameStart(1), + m_frameEnd(1), + m_quality(1.0f), + m_writerThreads(1), + m_ringBufferSize(DEFAULT_RINGBUFFER_SIZE), + m_bufferSize(0), + m_bufferSizeInBytes(0), + m_bufferStride(0), + m_pixelAspect(1.0f), + m_pixelScale(1.0f), + m_open(false), + m_stereo(false), + m_bound(false), + m_threadStop(false), + m_threadDone(false), + m_pbos(true), + m_texturePadding(0), + m_mappedBufferCount(0), + m_readBufferIndex(0), + m_readBufferCount(0), + m_writeBufferIndex(0), + m_writeBufferCount(0), + m_transferThread(0), + m_writerThread(0), + m_internalDataFormat(VideoDevice::RGB8), + m_fboInternalFormat(GL_RGB8), + m_textureFormat(GL_RGB), + m_textureType(GL_UNSIGNED_BYTE), + m_graph(graph), + m_audioInit(true), +#ifdef PLATFORM_DARWIN + m_immediateCopy(false) +#else + m_immediateCopy(true) +#endif +{ +} + +OutputVideoDevice::~OutputVideoDevice() +{ +} + +void +OutputVideoDevice::open(const StringVector& args) +{ + m_writer = 0; + m_open = true; + m_width = 1920; + m_height = 1080; + m_bits = 8; + m_float = false; + m_fps = 24; + m_pixelAspect = 1.0; + m_pixelScale = 1.0; + + m_pbos = true; + + options_description desc("Output Device Options"); + + desc.add_options() + ("help,h", "Usage Message") + ("verbose,v", "Verbose") + ("codec", value(), "Output Image Codec") + ("audio-codec", value(), "Output Audio Codec") + ("audio-rate", value(), "Output Audio Sample Rate") + ("audio-layout", value(), "Output Audio Channel Layout") + ("has-audio", value(), "Has Audio") + ("quality", value(), "Encoding Quality (float)") + ("comments", value(), "Comments") + ("copyright", value(), "Copyright") + ("output,o", value(), "Output File") + ("args", value()->multitoken(), "Output Writer Arguments") + ("chapterRanges", value()->multitoken(), "Chapter In/Out Pairs") + ("chapterTitles", value()->multitoken(), "Chapter Titles") + ("frame-start", value(), "Frame Start") + ("frame-end", value(), "Frame End") + ("size,s", value< vector >()->multitoken(), "Output Resolution WIDTH HEIGHT") + ("format,f", value(), "Format (RGB[A] + {8,10,12,16,32} + [F], e.g. RGB8)") + ("yuv", value(), "Output YUV sampling (e.g. 4:2:2)") + ("fps,f", value(), "Output FPS") + ("pixel-aspect,a", value(), "Output Pixel Aspect Ratio") + ("profile,p", "Output Debugging Profile (twk_output_profile_.dat)") + ("method,m", value(), "Method (dvp, sdvp, ipbo, ppbo, basic, p2p)") + ("threads", value(), "Writer Threads") + ("ring-buffer-size,s", value()->default_value(DEFAULT_RINGBUFFER_SIZE), "Ring Buffer Size"); + + variables_map vm; + + try + { + store(command_line_parser(args).options(desc).run(), vm); + store(parse_environment(desc, mapToEnvVar), vm); + notify(vm); + } + catch (std::exception& e) + { + cerr << "ERROR: OUTPUT_ARGS: " << e.what() << endl; + } + catch (...) + { + cerr << "ERROR: OUTPUT_ARGS: exception" << endl; + } + + if (vm.count("help") > 0) + { + cerr << endl << desc << endl; + TWK_THROW_EXC_STREAM("--help"); + } + + if (vm.count("size") > 0) + { + IntVector sizes = vm["size"].as(); + if (sizes.size() != 2) TWK_THROW_EXC_STREAM("--size WIDTH HEIGHT is required"); + + m_width = sizes[0]; + m_height = sizes[1]; + + if (m_width == 0 || m_height == 0) + { + TWK_THROW_EXC_STREAM("--size WIDTH HEIGHT sizes must be non-0"); + } + } + + if (vm.count("fps") > 0) + { + m_fps = vm["fps"].as(); + if (m_fps <= 0.0) TWK_THROW_EXC_STREAM("--fps FPS invalid value"); + } + + if (vm.count("pixel-aspect") > 0) + { + m_pixelAspect = vm["pixel-aspect"].as(); + if (m_pixelAspect <= 0.0) TWK_THROW_EXC_STREAM("--pixel-aspect FLOAT invalid value"); + } + + string dataType = "RGB8"; + m_float = false; + m_channels = 3; + m_bits = 8; + + if (vm.count("format") > 0) dataType = vm["format"].as(); + + if (dataType == "RGB8") { m_bits = 8; m_channels = 3; } + else if (dataType == "RGBA8") { m_bits = 8; m_channels = 4; } + else if (dataType == "RGB10") { m_bits = 10; m_channels = 3; } + else if (dataType == "RGB12") { m_bits = 12; m_channels = 3; } + else if (dataType == "RGB16") { m_bits = 16; m_channels = 3; } + else if (dataType == "RGBA16") { m_bits = 16; m_channels = 4; } + else if (dataType == "RGB16F") { m_bits = 16; m_channels = 3; m_float = true; } + else if (dataType == "RGBA16F") { m_bits = 16; m_channels = 4; m_float = true; } + else if (dataType == "RGB32F") { m_bits = 32; m_channels = 3; m_float = true; } + else if (dataType == "RGBA32F") { m_bits = 32; m_channels = 4; m_float = true; } + + m_filename = vm.count("output") > 0 ? vm["output"].as() : string("out.#.tif"); + + if (!(m_writer = TwkMovie::GenericIO::movieWriter(m_filename))) + { + m_open = false; + TWK_THROW_EXC_STREAM("Failed to open output video device " << name()); + } + + // + // Get the rest of the options + // + + m_infoFeedback = vm.count("verbose") > 0; + m_profile = vm.count("profile") > 0; + m_codec = vm.count("codec") > 0 ? vm["codec"].as() : ""; + m_audioCodec = vm.count("audio-codec") > 0 ? vm["audio-codec"].as() : ""; + m_audioRate = vm.count("audio-rate") > 0 ? vm["audio-rate"].as() : 48000.0; + m_comments = vm.count("comments") > 0 ? vm["comments"].as() : ""; + m_copyright = vm.count("copyright") > 0 ? vm["copyright"].as() : ""; + m_quality = vm.count("quality") > 0 ? vm["quality"].as() : 1.0; + m_writerThreads = vm.count("threads") > 0 ? vm["threads"].as() : 1; + m_frameStart = vm.count("frame-start") > 0 ? vm["frame-start"].as() : 1; + m_frameEnd = vm.count("frame-end") > 0 ? vm["frame-end"].as() : m_frameStart; + m_audioLayout = vm.count("audio-layout") > 0 ? + (TwkAudio::Layout) vm["audio-layout"].as() : TwkAudio::Stereo_2; + + if (vm.count("args") > 0) + { + StringVector args = vm["args"].as(); + + for (size_t i = 0; i < args.size(); i++) + { + StringVector buffer; + algorithm::split(buffer, args[i], is_any_of(string("=")), token_compress_on); + + if (buffer.size() == 1) + { + m_writerArgs.push_back(StringPair(buffer[0], "")); + } + else if (buffer.size() > 1) + { + m_writerArgs.push_back(StringPair(buffer[0], buffer[1])); + } + } + } + + if (vm.count("chapterRanges") > 0 && vm.count("chapterTitles") > 0) + { + IntVector ranges = vm["chapterRanges"].as(); + StringVector titles = vm["chapterTitles"].as(); + + if (ranges.size() != (2 * titles.size())) + { + m_open = false; + TWK_THROW_EXC_STREAM("Missmatched number of chapterRanges and chapterTitles"); + } + + m_info.chapters.resize(titles.size()); + for (size_t i = 0; i < titles.size(); i++) + { + m_info.chapters[i].title = titles[i]; + m_info.chapters[i].startFrame = ranges[(i*2)]; + m_info.chapters[i].endFrame = ranges[(i*2) + 1]; + } + } + + + initializeDataFormats(); + + // + // The MovieWriter will examine our m_info for some of its + // params so initialize it here. + // + + m_info.audio = vm.count("has-audio") > 0 ? bool(vm["has-audio"].as()) : false; + m_info.audioSampleRate = m_audioRate; + m_info.audioChannels = layoutChannels(m_audioLayout); + m_info.video = true; + m_info.start = m_frameStart; + m_info.end = m_frameEnd; + m_info.fps = m_fps; + m_info.inc = 1; + m_info.width = m_width; + m_info.height = m_height; + m_info.pixelAspect = m_pixelAspect; + m_info.uncropWidth = m_width; + m_info.uncropHeight = m_height; + m_info.uncropX = 0; + m_info.uncropY = 0; + m_info.numChannels = m_channels; + m_info.dataType = m_dataType; + + // + // Find out what the writer wants + // + + m_writerInfo = m_writer->open(m_info, m_filename, writeRequestFromState()); + + // + // XXX fields in m_writerInfo filled in by the writer should now + // override: m_channels, m_dataType, m_internalDataFormat. + // + // At a later date: m_width, m_height, possibly m_pixelAspect. + // + + switch (m_writerInfo.dataType) + { + case TwkFB::FrameBuffer::BIT: + m_bits = 1; + m_float = false; + break; + case TwkFB::FrameBuffer::UCHAR: + m_bits = 8; + m_float = false; + break; + default: + case TwkFB::FrameBuffer::USHORT: + m_bits = 16; + m_float = false; + break; + case TwkFB::FrameBuffer::UINT: + m_bits = 32; + m_float = false; + break; + case TwkFB::FrameBuffer::HALF: + m_bits = 16; + m_float = true; + break; + case TwkFB::FrameBuffer::FLOAT: + m_bits = 32; + m_float = true; + break; + case TwkFB::FrameBuffer::DOUBLE: + m_bits = 64; + m_float = true; + break; + case TwkFB::FrameBuffer::PACKED_R10_G10_B10_X2: + case TwkFB::FrameBuffer::PACKED_X2_B10_G10_R10: + m_bits = 10; + m_float = false; + break; + case TwkFB::FrameBuffer::PACKED_Cb8_Y8_Cr8_Y8: + case TwkFB::FrameBuffer::PACKED_Y8_Cb8_Y8_Cr8: + m_bits = 8; + m_float = false; + break; + } + m_channels = m_writerInfo.numChannels; + + initializeDataFormats(); + + if (m_dataType != m_writerInfo.dataType) + { + TWK_THROW_EXC_STREAM("Internal Data Format and writer goal format do not match!"); + } + + if (m_channels != m_writerInfo.numChannels) + { + TWK_THROW_EXC_STREAM("Number of channels and writer goal format do not match!"); + } + + m_channelNames.resize(m_channels); + + switch (m_channels) + { + case 1: + if (m_bits == 10) m_channelNames[0] = "RGB"; + break; + default: + case 3: + m_channelNames[0] = "R"; + m_channelNames[1] = "G"; + m_channelNames[2] = "B"; + break; + case 4: + m_channelNames[0] = "R"; + m_channelNames[1] = "G"; + m_channelNames[2] = "B"; + m_channelNames[3] = "A"; + break; + } + + // + // Internal state + // + + m_bufferSize = m_width * m_height * m_channels; + m_bufferSizeInBytes = m_width * m_height * pixelSizeInBytes(m_internalDataFormat); + m_bufferStride = m_width * pixelSizeInBytes(m_internalDataFormat); + + if (m_infoFeedback) cerr << "INFO: " << m_width << "x" << m_height << "@" << m_fps << endl; + + m_fboInternalFormat = TwkGLF::internalFormatFromDataFormat(m_internalDataFormat); + GLenumPair epair = TwkGLF::textureFormatFromDataFormat(m_internalDataFormat); + m_textureFormat = epair.first; + m_textureType = epair.second; + m_texturePadding = 0; + if (m_textureType == GL_UNSIGNED_INT_10_10_10_2) m_textureType = GL_UNSIGNED_INT_2_10_10_10_REV; + + m_videoChannels.clear(); + + m_videoChannels.push_back(new VideoChannel(m_bufferSizeInBytes, m_ringBufferSize)); + + if (m_stereo) + { + m_videoChannels.push_back(new VideoChannel(m_bufferSizeInBytes, m_ringBufferSize)); + } +} + +void +OutputVideoDevice::close() +{ + if (m_open) + { + if (m_bound) unbind(); + m_open = false; + } + + TwkGLF::GLBindableVideoDevice::close(); +} + +namespace { + +struct ThreadTrampoline +{ + ThreadTrampoline(OutputVideoDevice* d, bool tthread) + : device(d), transferThread(tthread) {} + void operator()() + { + GC_EXCLUDE_THIS_THREAD; + + if (transferThread) + { + setThreadName("OVD Transfer"); + device->transferMain(); + } + else + { + setThreadName("OVD Writer"); + device->writerMain(); + } + } + + bool transferThread; + OutputVideoDevice* device; +}; + +} + +VideoDevice::Time OutputVideoDevice::inputTime() const { return 0; } +bool OutputVideoDevice::isOpen() const { return m_open; } +void OutputVideoDevice::beginTransfer() const { } +void OutputVideoDevice::endTransfer() const { } +void OutputVideoDevice::makeCurrent() const { } +void OutputVideoDevice::clearCaches() const { } +void OutputVideoDevice::syncBuffers() const { } + +VideoDevice::Time +OutputVideoDevice::outputTime() const +{ + lockDevice(true, "OUTPUT TIME"); + Time t = VideoDevice::outputTime(); + lockDevice(false); + return t; +} + +bool +OutputVideoDevice::isStereo() const +{ + return false; +} + +bool +OutputVideoDevice::isDualStereo() const +{ + return false; +} + +bool +OutputVideoDevice::willBlockOnTransfer() const +{ + return false; +} + +void +OutputVideoDevice::bind(const TwkGLF::GLVideoDevice* device) const +{ + if (!m_open) return; + + m_threadStop = false; + m_threadDone = false; + + m_gpuTimes.clear(); + m_transferTimes.clear(); + m_globalTimer.start(); + + + if (m_pbos) + { + // + // Generate the PBO buffers + // + + for (size_t q = 0; q < m_videoChannels.size(); q++) + { + VideoChannel* vc = m_videoChannels[q]; + + for (size_t i = 0; i < m_ringBufferSize; i++) + { + FrameData& f = vc->data[i]; + + glGenBuffers(1, &f.globject); TWK_GLDEBUG; + glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, f.globject); TWK_GLDEBUG; + glBufferData(GL_PIXEL_PACK_BUFFER_ARB, m_bufferSizeInBytes, NULL, GL_DYNAMIC_READ); TWK_GLDEBUG; + glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, 0); TWK_GLDEBUG; + f.state = FrameData::Ready; + } + } + } + else // readpixels + { + for (size_t q = 0; q < m_videoChannels.size(); q++) + { + VideoChannel* vc = m_videoChannels[q]; + + for (size_t i = 0; i < m_ringBufferSize; i++) + { + FrameData& f = vc->data[i]; + f.state = FrameData::Ready; + } + } + } + + resetClock(); + m_readBufferIndex = 0; + m_readBufferCount = 0; + m_writeBufferIndex = 0; + m_writeBufferCount = 0; + m_bound = true; + + m_transferThread = new Thread(ThreadTrampoline(const_cast(this), true)); + m_writerThread = new Thread(ThreadTrampoline(const_cast(this), false)); +} + +void +OutputVideoDevice::bind2(const TwkGLF::GLVideoDevice* device0, + const TwkGLF::GLVideoDevice* device1) const +{ + bind(device0); +} + +void +OutputVideoDevice::unbind() const +{ + if (!m_bound) return; + + // + // If we're using PBOs there may be a transfer in progress. This means + // transferMain() will be blocked waiting to transfer to the queue, and + // the writer thread will be blocked waiting for the queue to have + // something in it. + // + + if (m_pbos) + { + finalizeOutstandingPBOTransfer(m_videoChannels[0]); + if (m_stereo) finalizeOutstandingPBOTransfer(m_videoChannels[1]); + } + + // + // Now there should be nothing blocking writer thread, so wait for it to + // complete. + // + + m_writerThread->join(); + + m_globalTimer.stop(); + + // + // Shutdown transfer thread + // + + lockDevice(true, "UNBIND"); + m_threadStop = true; + lockDevice(false); + + lockDevice(true, "UNBIND"); + bool threadDone = m_threadDone; + lockDevice(false); + + // + // Unlock any images that were locked by the reader thread. + // + + for (size_t q = 0; q < m_videoChannels.size(); q++) + { + VideoChannel* vc = m_videoChannels[q]; + + for (size_t i = 0; i < vc->data.size(); i++) + { + // + // Careful with the locking order here. If the thread is + // in the middle of a dvp transfer, or other operation, + // we don't want to pull the rug out from under it until + // after its completed. + // + + FrameData& f = vc->data[i]; + f.lockState("UNBIND"); + if (f.state == FrameData::Reading && f.locked) f.unlockImage(); + f.unlockState(); + f.lockImage("UNBIND"); + f.fbo = 0; + f.unlockImage(); + } + } + + if (!threadDone) + { + m_transferThread->join(); + delete m_transferThread; + m_transferThread = 0; + + m_writerThread->join(); + delete m_writerThread; + m_writerThread = 0; + } + + // + // Delete all allocated resources + // + + for (size_t q = 0; q < m_videoChannels.size(); q++) + { + VideoChannel* vc = m_videoChannels[q]; + + for (size_t i = 0; i < vc->data.size(); i++) + { + FrameData& f = vc->data[i]; + + if (m_pbos && !m_immediateCopy && f.state == FrameData::Mapped) + { + glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, f.globject); TWK_GLDEBUG; + glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB); TWK_GLDEBUG; + f.globject = 0; + } + + if (m_pbos) + { + glDeleteBuffers(1, &f.globject); TWK_GLDEBUG; + } + + if (f.imageData) + { + TWK_DEALLOCATE_ARRAY(f.imageData); + f.imageData = 0; + } + } + } + + m_bound = false; + + if (m_profile) + { + if (!m_gpuTimes.empty()) + { + Time accumTime = 0; + Time minTime = numeric_limits::max(); + Time maxTime = -numeric_limits::max(); + + for (size_t i = 5; i < m_gpuTimes.size(); i++) + { + Time t = m_gpuTimes[i]; + accumTime += t; + minTime = std::min(minTime, t); + maxTime = std::max(maxTime, t); + } + + cerr << "INFO: GPU: " << (accumTime / m_gpuTimes.size()) + << ", min=" << minTime + << ", max=" << maxTime + << ", count=" << m_gpuTimes.size() + << endl; + } + + if (!m_transferTimes.empty()) + { + Time accumTime = 0; + Time minTime = numeric_limits::max(); + Time maxTime = -numeric_limits::max(); + + for (size_t i = 5; i < m_transferTimes.size(); i++) + { + Time t = m_transferTimes[i]; + accumTime += t; + minTime = std::min(minTime, t); + maxTime = std::max(maxTime, t); + } + + cerr << "INFO: TRANSFER: " << (accumTime / m_transferTimes.size()) + << ", min=" << minTime + << ", max=" << maxTime + << ", count=" << m_transferTimes.size() + << endl; + } + + if (!m_gpuTimes.empty() && !m_transferTimes.empty()) + { + ostringstream filename; + filename << "twk_aja_profile_" << TwkUtil::processID() << ".csv"; + ofstream file(filename.str().c_str()); + size_t count = m_gpuTimes.size(); + + file << "GPUStart,GPUDuration,NTV2Begin,NTV2Duration" << endl; + + for (size_t i = 0, s = std::min(m_gpuTimes.size(), m_transferTimes.size()); i < s; i++) + { + file << m_gpuBeginTime[i] << "," + << m_gpuTimes[i] << "," + << m_transferBeginTime[i] << "," + << m_transferTimes[i] << endl; + } + } + } +} + +void +OutputVideoDevice::transferChannel(size_t n, const GLFBO* fbo) const +{ + VideoChannel* vc = m_videoChannels[n]; + FrameData& fdread = vc->data[m_readBufferIndex]; + + fbo->bind(); + fbo->beginExternalReadback(); + + if (m_pbos) transferChannelPBO(vc, fbo); + else transferChannelReadPixels(vc, fbo); +} + + +void +OutputVideoDevice::transfer(const TwkGLF::GLFBO* fbo) const +{ + if (!m_open) return; + + transferChannel(0, fbo); + + lockDevice(true, "READER in transfer"); + m_readBufferCount++; + m_readBufferIndex = m_readBufferCount % m_ringBufferSize; + incrementClock(); + + lockDevice(false); +} + +void +OutputVideoDevice::transfer2(const TwkGLF::GLFBO* fbo0, + const TwkGLF::GLFBO* fbo1) const +{ +} + +size_t +OutputVideoDevice::asyncMaxMappedBuffers() const +{ + return m_ringBufferSize; +} + +VideoDevice::Time +OutputVideoDevice::deviceLatency() const +{ + return Time(0.0); +} + +void +OutputVideoDevice::lockDevice(bool lock, const char* threadName) const +{ + if (lock) + { + Timer timer; + timer.start(); + m_deviceMutex.lock(); + Time t = timer.elapsed(); + if (t > 0.001 && m_infoFeedback) cerr << "INFO: " << threadName << ": lockDevice for " << t << endl; + } + else + { + m_deviceMutex.unlock(); + } +} + +size_t OutputVideoDevice::numVideoFormats() const { return 1; } +size_t OutputVideoDevice::currentVideoFormat() const { return 0; } +void OutputVideoDevice::setVideoFormat(size_t i) { } +size_t OutputVideoDevice::numDataFormats() const { return 1; } +void OutputVideoDevice::setDataFormat(size_t i) { } +size_t OutputVideoDevice::currentDataFormat() const { return 0; } +size_t OutputVideoDevice::numSyncModes() const { return 0; } +OutputVideoDevice::SyncMode OutputVideoDevice::syncModeAtIndex(size_t i) const { return SyncMode(""); } +void OutputVideoDevice::setSyncMode(size_t i) { } +size_t OutputVideoDevice::currentSyncMode() const { return 0; } +size_t OutputVideoDevice::numSyncSources() const { return 0; } +void OutputVideoDevice::setSyncSource(size_t i) { } +size_t OutputVideoDevice::currentSyncSource() const { return 0; } + +OutputVideoDevice::VideoFormat +OutputVideoDevice::videoFormatAtIndex(size_t index) const +{ + return VideoFormat(m_width, m_height, m_pixelAspect, m_pixelScale, m_fps, ""); +} + +OutputVideoDevice::DataFormat +OutputVideoDevice::dataFormatAtIndex(size_t i) const +{ + return DataFormat(m_internalDataFormat, ""); +} + +OutputVideoDevice::SyncSource +OutputVideoDevice::syncSourceAtIndex(size_t i) const +{ + return SyncSource(""); +} + +size_t OutputVideoDevice::numAudioFormats() const { return 1; } +size_t OutputVideoDevice::currentAudioFormat() const { return 0; } +void OutputVideoDevice::setAudioFormat(size_t i) { } + +void +OutputVideoDevice::audioFrameSizeSequence(AudioFrameSizeVector& fsizes) const +{ +} + +OutputVideoDevice::AudioFormat +OutputVideoDevice::audioFormatAtIndex(size_t index) const +{ + return AudioFormat(); +} + +VideoDevice::Resolution +OutputVideoDevice::resolution() const +{ + return Resolution(m_width, m_height, m_pixelAspect, m_pixelScale); +} + +VideoDevice::Timing +OutputVideoDevice::timing() const +{ + return Timing(m_fps); +} + +VideoDevice::VideoFormat +OutputVideoDevice::format() const +{ + return VideoFormat(m_width, m_height, m_pixelAspect, m_pixelScale, m_fps, ""); +} + +size_t +OutputVideoDevice::width() const +{ + return m_width; +} + +size_t +OutputVideoDevice::height() const +{ + return m_height; +} + +void +OutputVideoDevice::transferAudio(void* interleavedData, size_t n) const +{ +} + +namespace { + +// +// In order to do this with a shader and have it be efficient we have to +// write the shader to completely pack the subsampled data with no +// scanline padding. Until we have that this will suffice. +// +// NOTE: this is discarding every other chroma sample instead of +// interpolating. Not the best way to do this. +// + +#ifdef _MSC_VER +#define restrict +#else +// Ref.: https://en.wikipedia.org/wiki/Restrict +// C++ does not have standard support for restrict, but many compilers have equivalents that +// usually work in both C++ and C, such as the GCC's and Clang's __restrict__, and +// Visual C++'s __declspec(restrict). In addition, __restrict is supported by those three +// compilers. The exact interpretation of these alternative keywords vary by the compiler: +#define restrict __restrict +#endif + +void subsample422_8bit_UYVY(int width, int height, unsigned char* buffer) +{ + Timer timer; + timer.start(); + + unsigned char* p1 = buffer; + + if (width % 6 == 0) + { + for (size_t row = 0; row < height; row++) + { + for (unsigned char* restrict p0 = buffer + row * 3 * width, * restrict e = p0 + 3 * width; + p0 < e; + p0 += 6, p1 += 4) + { + // + // The comment out parts do the interpolation, but this + // can be done as a small blur after the YUV + // conversion. That saves a few microseconds + // + + //p1[0] = (int(p0[1]) + int(p0[4])) >> 1; + p1[0] = p0[1]; + p1[1] = p0[0]; + //p1[2] = (int(p0[2]) + int(p0[5])) >> 1; + p1[2] = p0[2]; + p1[3] = p0[3]; + } + } + } + else + { + for (size_t row = 0; row < height; row++) + { + size_t count = 0; + + for (unsigned char* restrict p0 = buffer + row * 3 * width, * restrict e = p0 + 3 * width; + p0 < e; + p0 += 3, count++) + { + *p1 = p0[count % 2 + 1]; p1++; + *p1 = *p0; p1++; + } + } + } + + timer.stop(); + //cout << timer.elapsed() << endl; +} + +#define R10MASK 0x3FF00000 +#define G10MASK 0xFFC00 +#define B10MASK 0x3FF + +void subsample422_10bit(int width, int height, uint32_t* buffer) +{ + Timer timer; + timer.start(); + uint32_t* p1 = buffer; + + // + // NOTE: 2_10_10_10_INT_REV is *backwards* eventhough its GL_RGB + // (hence the REV). So Y is the lowest sig bits. + // + + for (size_t row = 0; row < height; row++) + { + for (uint32_t* restrict p0 = buffer + row * width, * restrict e = p0 + width; + p0 < e; + p0 += 6) + { + const uint32_t A = *p0; + const uint32_t B = p0[1]; + const uint32_t C = p0[2]; + const uint32_t D = p0[3]; + const uint32_t E = p0[4]; + const uint32_t F = p0[5]; + + *p1 = (A & R10MASK) | ((A << 10) & G10MASK) | ((A >> 10) & B10MASK); p1++; + *p1 = ((C << 20) & R10MASK) | (B & G10MASK) | (B & B10MASK); p1++; + *p1 = ((C << 10) & R10MASK) | ((D << 10) & G10MASK) | ((B >> 20) & B10MASK); p1++; + *p1 = ((F << 20) & R10MASK) | ((D >> 10) & G10MASK) | (E & B10MASK); p1++; + } + } + + timer.stop(); + //cout << timer.elapsed() << endl; +} + +} + + +void +OutputVideoDevice::transferMain() +{ + lockDevice(true); + lockDevice(false); + + lockDevice(true, "TRANSFER"); + bool threadStop = m_threadStop; + lockDevice(false); + + bool sleeping = false; + size_t spinCount = 0; + + size_t nchannels = 1; + + for (size_t vi = 0; + !threadStop; + vi = (vi + 1) % nchannels) + { + VideoChannel* vc = m_videoChannels[vi]; + size_t vi2 = (vi + 1) % nchannels; + VideoChannel* vc2 = m_videoChannels[vi2]; + + sleeping = false; + const bool lastChannel = vi == nchannels-1; + + bool wrote = false; + + // + // Maybe transfer frame + // + + lockDevice(true, "TRANSFER"); + size_t wi = m_writeBufferIndex; + size_t wi2 = (wi + 1) % m_ringBufferSize; + FrameData& f = vc->data[wi]; + FrameData& f2 = vc2->data[wi2]; + lockDevice(false); + + f.lockState("TRANSFER"); + const bool readyForTransfer = f.state == FrameData::Mapped; + const bool reading = f.state == FrameData::Reading; + const bool shouldLock = readyForTransfer | reading; + f.unlockState(); + + if (shouldLock) + { + f.lockImage("TRANSFER"); + + // + // Update threadStop again in case we were blocked + // + + lockDevice(true, "TRANSFER"); + threadStop = m_threadStop; + lockDevice(false); + + if (!threadStop) + { + f.lockState("TRANSFER"); + f.state = FrameData::Transfering; + f.unlockState(); + + // + // If the output plugin requested a subsampled format do the + // subsampling here. + // XXX as of now we are doing any necessary subsampling in the + // output plugin, but it should be done here to avoid + // duplication, and because we can eventually push it into + // hardware (OpenCL or whatever). + // + + switch (m_internalDataFormat) + { + case Y0CbY1Cr_8_422: + //subsample422_8bit_UYVY(m_width, m_height, (unsigned char*)t.videoBuffer); + break; + case YCrCb_AJA_10_422: + //subsample422_10bit(m_width, m_height, (uint32_t*)t.videoBuffer); + break; + default: + break; + } + + // + // Add to writer queue. imagesAtFrame() below will pull off + // queue and make FB for output plugin. + // + + { + ScopedLock lock(m_fdQueueMutex); + m_fdQueue.push_back(&f); + m_fdQueueCond.notify_all(); + } + + //f.fbo->endExternalReadback(); + + wrote = true; + f.lockState("TRANSFER"); + f.state = FrameData::NeedsUnmap; + f.unlockState(); + } + + f.unlockImage(); + } + + lockDevice(true, "TRANSFER"); + + if (wrote && lastChannel) + { + m_writeBufferCount++; + m_writeBufferIndex = m_writeBufferCount % m_ringBufferSize; + } + + threadStop = m_threadStop; + lockDevice(false); + + if (!wrote) + { + vi--; + } + } + + // + // Shutdown ring buffer and exit thread + // + + lockDevice(true, "TRANSFER"); + m_threadDone = true; + lockDevice(false); +} + +void +OutputVideoDevice::finalizeOutstandingPBOTransfer(VideoChannel* vc) const +{ + // + // Transfer last read PBO or pinned buffer + // + // NOTE: we don't have to lock the device to read m_readBufferIndex + // because only this thread is allowed to change it. + // + + if (m_readBufferCount <= 0) return; + + const size_t previousReadIndex = (m_readBufferIndex + m_ringBufferSize - 1) % m_ringBufferSize; + + FrameData& fdprev = vc->data[previousReadIndex]; + + fdprev.lockState("PBO READER"); + bool reading = fdprev.state == FrameData::Reading; + fdprev.unlockState(); + assert(reading); + + glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, fdprev.globject); TWK_GLDEBUG; + GLubyte* p = (GLubyte*)glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY_ARB); TWK_GLDEBUG; + + if (p) + { + if (m_immediateCopy) + { + if (!fdprev.imageData) + { + fdprev.imageData = TWK_ALLOCATE_ARRAY_PAGE_ALIGNED(unsigned char, m_bufferSizeInBytes); + } + memcpy(fdprev.imageData, p, m_bufferSizeInBytes); + } + else + { + fdprev.mappedPointer = p; + } + + fdprev.lockState("PBO READER"); + if (fdprev.mappedPointer || fdprev.imageData) fdprev.state = FrameData::Mapped; + if (m_immediateCopy) glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB); TWK_GLDEBUG; + //fdprev.fbo = 0; + fdprev.unlockState(); + } + else + { + fdprev.imageData = 0; + fdprev.mappedPointer = 0; + //fdprev.fbo = 0; + glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB); TWK_GLDEBUG; + cerr << "ERROR: not mapped" << endl; + } + + endGPUTransfer(); + + m_mappedBufferCount++; + fdprev.locked = false; + fdprev.unlockImage(); +} + +void +OutputVideoDevice::transferChannelPBO(VideoChannel* vc, const GLFBO* fbo) const +{ + finalizeOutstandingPBOTransfer(vc); + + // + // Initiate next PBO read. + // + // NOTE: we don't have to lock the device to read + // m_readBufferIndex because only this thread is allowed to change + // it. + // + + FrameData& fdread = vc->data[m_readBufferIndex]; + + // + // May block at this point if transferring + // + + fdread.lockImage("PBO READER"); // until unmapped above + fdread.locked = true; + + fdread.lockState("PBO READER"); + bool transfer = fdread.state == FrameData::Transfering; + bool unmapit = fdread.state == FrameData::NeedsUnmap || transfer; + bool mapped = fdread.state == FrameData::Mapped; + if (!mapped) + { + fdread.state = FrameData::Reading; + fdread.fbo = fbo; + } + fdread.unlockState(); + + if (mapped) + { + // + // This can happen in the non immediateCopy case. We're + // basically about to read over an already mapped PBO. + // + // Prob can't happen anymore now that renderer waits until + // external readback happens on FBOs. + // + + while (mapped) + { + fdread.locked = false; + fdread.unlockImage(); + fdread.lockState("PBO READER WAIT"); + transfer = fdread.state == FrameData::Transfering; + unmapit = fdread.state == FrameData::NeedsUnmap || transfer; + mapped = fdread.state == FrameData::Mapped; + fdread.unlockState(); +#ifndef PLATFORM_WINDOWS + usleep(1); +#endif + fdread.lockImage("PBO READER"); + fdread.locked = true; + } + + fdread.lockState("PBO READER"); + fdread.state = FrameData::Reading; + fdread.fbo = fbo; + fdread.unlockState(); + } + + glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, fdread.globject); TWK_GLDEBUG; + + if (unmapit) + { + if (!m_immediateCopy) glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB); TWK_GLDEBUG; + fdread.mappedPointer = 0; + m_mappedBufferCount--; + } + + startGPUTransfer(); + + glReadPixels(0, + 0, + m_width, + m_height, + m_textureFormat, + m_textureType, + 0); TWK_GLDEBUG; + + glBindBuffer(GL_PIXEL_PACK_BUFFER, 0); +} + +void +OutputVideoDevice::transferChannelReadPixels(VideoChannel* vc, const GLFBO* fbo) const +{ + // + // Vanillia glReadPixels case (only for comparison) + // + + FrameData& fdread = vc->data[m_readBufferIndex]; + + fdread.lockImage("READPIXELS READER"); + fdread.lockState("READPIXELS READER"); + fdread.state = FrameData::Reading; + fdread.fbo = fbo; + if (!fdread.imageData) + fdread.imageData = TWK_ALLOCATE_ARRAY_PAGE_ALIGNED(unsigned char, m_bufferSizeInBytes); + fdread.unlockState(); + + startGPUTransfer(); + + glReadPixels(0, + 0, + m_width, + m_height, + m_textureFormat, + m_textureType, + fdread.imageData); TWK_GLDEBUG; + + endGPUTransfer(); + + fdread.lockState("READPIXELS READER"); + fdread.state = FrameData::Mapped; + fdread.fbo = 0; + fdread.unlockState(); + fdread.unlockImage(); +} + +void +OutputVideoDevice::startGPUTransfer() const +{ + if (m_profile) m_gpuBeginTime.push_back(m_globalTimer.elapsed()); +} + +void +OutputVideoDevice::endGPUTransfer() const +{ + if (m_profile) m_gpuTimes.push_back(m_globalTimer.elapsed() - m_gpuBeginTime.back()); +} + +void +OutputVideoDevice::startOutputTransfer() const +{ + if (m_profile) m_transferBeginTime.push_back(m_globalTimer.elapsed()); +} + +void +OutputVideoDevice::endOutputTransfer() const +{ + if (m_profile) m_transferTimes.push_back(m_globalTimer.elapsed() - m_transferBeginTime.back()); +} + +void +OutputVideoDevice::packBufferCopy(unsigned char* src, size_t srcRowSize, + unsigned char* dst, size_t dstRowSize, + size_t rows) +{ + for (size_t row = 0; row < rows; row++) + { + memcpy(dst + dstRowSize * row, + src + dstRowSize * row, + dstRowSize); + } +} + +TwkMovie::MovieWriter::WriteRequest +OutputVideoDevice::writeRequestFromState() const +{ + MovieWriter::WriteRequest request; + request.verbose = m_infoFeedback; + request.threads = m_writerThreads; + request.fps = m_fps; + request.compression = m_codec; + request.codec = m_codec; + request.audioCodec = m_audioCodec; + request.quality = m_quality; + request.pixelAspect = m_pixelAspect; + request.audioChannels = channelsCount(m_audioLayout); + request.audioRate = m_audioRate; + request.stereo = false; + request.comments = m_comments; + request.copyright = m_copyright; + request.parameters = m_writerArgs; + + //if (rysamples || usamples) + //{ + //request.preferCommonFormat = false; + //request.keepPlanar = true; + //} + + return request; +} + +void +OutputVideoDevice::writerMain() +{ + m_writer->write(this); +} + +// +// XXX imagesAtFrame always sends the next frame provided by the renderer +// regardless of the requested frame number. +// + +void +OutputVideoDevice::imagesAtFrame(const ReadRequest& request, FrameBufferVector& fbs) +{ + ScopedLock lock(m_fdQueueMutex); + while (m_fdQueue.empty()) m_fdQueueCond.wait(lock); + + FrameData* fd = m_fdQueue.front(); + m_fdQueue.pop_front(); + + fbs.resize(1); + if (!fbs.front()) fbs.front() = new FrameBuffer(); + FrameBuffer& fb = *fbs.front(); + + fb.restructure(m_width, + m_height, + 1, + m_channels, + m_dataType, + m_immediateCopy ? fd->imageData : fd->mappedPointer, + &m_channelNames, + TwkFB::FrameBuffer::BOTTOMLEFT, + m_immediateCopy); // delete on destruction + + // + // Right now we can't know when to release the GLFBO from + // external readback because of the way the MovieWriter API + // works: the MovieWriter has the control loop and periodically + // calls this function to get another frame to + // write. Unfortunately, there's no way currently to know when it + // actually finished writing. One way we could potentially do + // that is to have some kind of a signal emitted by the FB when + // its "done", but it seems like we need to have an alternate or + // new writer API to make this efficient + // + // So to get around the above problem we just copy the pixels. In + // In the immedate copy mode fd->imageData has unique memory so + // we just adopt it. + // + + if (m_immediateCopy) + { + // + // Should we just reallocate here in this thread? This forces + // the rendering thread to do the memory allocation. + // + + fd->imageData = 0; + } + else + { + fb.ownData(); // copies them now (in this thread) + } + + // + // This will unlock the GLFBO for reuse by the render thread. + // + + fd->fbo->endExternalReadback(); +} + +size_t +OutputVideoDevice::audioFillBuffer(const AudioReadRequest& request, AudioBuffer& buf) +{ + size_t nsamps = TwkAudio::timeToSamples(request.duration, m_audioRate); + buf.reconfigure(nsamps, m_audioLayout, m_audioRate, request.startTime); + IPCore::IPNode::AudioContext context(buf, m_fps); + + if (m_audioInit) + { + // + // Initialize as late as possible (which is right before we + // actually need the audio). + // + + IPCore::IPGraph::AudioConfiguration config( + m_audioRate, + m_audioLayout, + nsamps, + m_fps, + false, // XXX arbitarily say we're _not_ going backwards. + buf.startSample(), + size_t((m_frameEnd - m_frameStart) / m_fps * m_audioRate + 0.49)); + + m_graph->audioConfigure(config); + m_audioInit = false; + } + + return m_graph->audioFillBuffer(context); +} + +void +OutputVideoDevice::initializeDataFormats() +{ + switch (m_bits) + { + default: + case 8: + m_internalDataFormat = m_channels == 3 ? RGB8 : RGBA8; + m_dataType = TwkFB::FrameBuffer::UCHAR; + break; + case 10: + m_internalDataFormat = RGB10X2Rev; + m_dataType = TwkFB::FrameBuffer::PACKED_X2_B10_G10_R10; + m_channels = 1; + break; + case 16: + if (m_float) + { + m_internalDataFormat = m_channels == 3 ? RGB16F : RGBA16F; + m_dataType = TwkFB::FrameBuffer::HALF; + } + else + { + m_internalDataFormat = m_channels == 3 ? RGB16 : RGBA16; + m_dataType = TwkFB::FrameBuffer::USHORT; + } + break; + case 32: + m_internalDataFormat = m_channels == 3 ? RGB32F : RGBA32F; + m_dataType = TwkFB::FrameBuffer::FLOAT; + break; + } +} + +} // OutputVideoDevices diff --git a/src/lib/app/OutputVideoDevices/OutputVideoDeviceModule.cpp b/src/lib/app/OutputVideoDevices/OutputVideoDeviceModule.cpp new file mode 100644 index 000000000..3048a284a --- /dev/null +++ b/src/lib/app/OutputVideoDevices/OutputVideoDeviceModule.cpp @@ -0,0 +1,76 @@ +// +// Copyright (c) 2011 Tweak Software. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +#include +#include +#include +#include +#ifdef PLATFORM_DARWIN +#include +#endif +#ifdef PLATFORM_WINDOWS +#include +#include +#include +#include +#endif +#include + +namespace OutputVideoDevices { +using namespace std; + +OutputVideoDeviceModule::OutputVideoDeviceModule(NativeDisplayPtr p) + : VideoModule() +{ + open(); +} + +OutputVideoDeviceModule::~OutputVideoDeviceModule() +{ + close(); +} + +string +OutputVideoDeviceModule::name() const +{ + return "Output"; +} + +void +OutputVideoDeviceModule::open() +{ + if (isOpen()) return; + + //m_devices.push_back(new OutputVideoDeviceModule(this, "foo")); +} + +void +OutputVideoDeviceModule::close() +{ + for (size_t i = 0; i < m_devices.size(); i++) + { + m_devices[i]->close(); + delete m_devices[i]; + } + m_devices.clear(); +} + +bool +OutputVideoDeviceModule::isOpen() const +{ + return !m_devices.empty(); +} + +OutputVideoDevice* +OutputVideoDeviceModule::newDevice(const string& name, IPCore::IPGraph* graph) +{ + OutputVideoDevice* d = new OutputVideoDevice(this, name, graph); + m_devices.push_back(d); + return d; +} + +} // OutputVideoDevices diff --git a/src/lib/app/OutputVideoDevices/OutputVideoDevices/OutputVideoDevice.h b/src/lib/app/OutputVideoDevices/OutputVideoDevices/OutputVideoDevice.h new file mode 100644 index 000000000..7f3eedb6a --- /dev/null +++ b/src/lib/app/OutputVideoDevices/OutputVideoDevices/OutputVideoDevice.h @@ -0,0 +1,323 @@ +// +// Copyright (c) 2014 Tweak Software. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +#ifndef __OutputVideoDevices__OutputVideoDevice__h__ +#define __OutputVideoDevices__OutputVideoDevice__h__ +#include +#include +#include +#include + +#if defined(PLATFORM_WINDOWS) +#include +#include +#include +#endif + +#if defined(PLATFORM_LINUX) +#include +#include +#include +#include +#include +#endif + +#if defined(PLATFORM_DARWIN) +#include +#endif + +#include +#include +#include +#include +#include +#include + +namespace IPCore { +class IPGraph; +} + +namespace OutputVideoDevices { +typedef boost::mutex::scoped_lock ScopedLock; +typedef boost::mutex Mutex; +typedef boost::condition_variable Condition; +class OutputVideoDeviceModule; + +// +// OutputVideoDevice +// +// OutputVideoDevice uses the video device API from TwkApp::VideoDevice to +// have images streamed to it. Because its also a TwkMovie::Movie object +// it can be connected to any of the media output plugins. In other words: +// it tries to stream images and audio as fast as possible to the media +// writers. +// +// The general layout of the code is similar to how the SDI +// works with the exception that this VideoDevice will not take per-frame +// audio like SDI does. For audio it interfaces directly with the +// graph. There is a ring buffer of frames being transfered from the GPU +// just like the SDI devices. +// +// The idea is to stream *as fast as possible* directly from the GPU just +// like SDI. So unlike rvio, this avoids any kind of software manipulation +// of the pixels. +// +// The media writing is done in a separate thread which has to be +// synchronized with the render thread. +// +// In the future we should find a way to break this up so that we don't +// have a TwkApp::VideoDevice which is also a TwkMovie::Movie. +// + + +class OutputVideoDevice : public TwkGLF::GLBindableVideoDevice, + public TwkMovie::Movie +{ +public: + typedef std::vector StringVector; + typedef std::pair StringPair; + typedef std::vector StringPairVector; + typedef VideoDevice::Time Time; + typedef boost::thread Thread; + typedef TwkUtil::Timer Timer; + typedef TwkGLF::GLFence GLFence; + typedef std::vector BufferVector; + typedef std::vector"); + return Struct(reverse(members)); + } + + \: parseArray (Value; ParseState state) + { + state.nextIf(""); + [Value] list = nil; + + // + // Allow for mal-formed xml from shotgrid. + // + if (state.line() == "") state.next(); + else + { + state.nextIf(""); + + while (state.line() != "") + { + list = parseValue(state) : list; + } + state.nextIf(""); + } + state.nextIf(""); + + if (list eq nil) return EmptyArray; + + return Array(reverse(list)); + } + + \: parseValue (Value; ParseState state) + { + let skipClosingValue = regex("^").match(state.line()); + if (!skipClosingValue) state.nextIf(""); + + string line = state.line(); + Value value = ErrorValue; + + case (line) + { + "" -> { value = parseStruct(state); } + "" -> { value = parseArray(state); } + "" -> { value = Nil; state.next(); } + + _ -> + { + // + // Scalar tags + // + + let re = regex("(<[^>]+>)([^<]*)(]+>)"), + parts = re.smatch(line), + start = parts[1], + body = parts[2], + end = parts[3]; + + case (start) + { + "" -> { value = Int(int(body)); } + "" -> { value = Int(int(body)); } + "" -> { value = String(body); } + "" -> { value = Bool(int(body) == 1); } + "" -> { value = Double(float(body)); } + + "" -> + { + let dre = regex("([0-9]{4})-?([0-9]{2})-?([0-9]{2})[Tt]([0-9]{2}):([0-9]{2}):([0-9]{2})"), + d = dre.smatch(body); + + assert(d.size() == 7); + + value = DateTime((int(d[1]), int(d[2]), int(d[3]), + int(d[4]), int(d[5]), int(d[6]))); + } + + "" -> + { + value = Binary(from_base64(string_to_utf8(body))); + } + + _ -> + { + throw exception("XML parser failure in xmlrpc: unknown tag %s" % start); + } + } + + state.next(); + } + } + + if (!skipClosingValue) state.nextIf(""); + return value; + } + + \: parseParam (Value; ParseState state) + { + state.nextIf(""); + let v = parseValue(state); + state.nextIf(""); + + return v; + } + + // + // Top level + // + + for_index (i; lines) + { + let line = lines[i]; + + if (line == "") + { + let Struct [ (_, Int code), (_, String why) ] = parseValue(ParseState(i+1, lines)); + print("ERROR: XML-RPC: faultCode=%d -- %s\n" % (code, why)); + break; + } + + if (line == "") return parseParam(ParseState(i+1, lines)); + } + + return Nil; +} + +global (int,float)[] inFlight; + +function: addInFlight(int hash) +{ + inFlight.push_back((hash, commands.theTime())); +} + +function: removeInFlight(int hash) +{ + let index = -1; + for_index(i; inFlight) if (inFlight[i]._0 == hash) index = i; + if (index != -1) inFlight.erase(index,1); +} + +function: inFlightStats((int, float); ) +{ + if (inFlight.size() != 0) return (inFlight.size(), inFlight.back()._1); + else return (0, 0.0); +} + +documentation: """ +call() takes the url of the server, the name of the method to call, a list +of Value trees for the parameters, and a function to call with the result +when it is available. + +call() will return immediately. The rvalFunc will be called at some later +point. +"""; + + +function: call (void; + string url, + string name, + [Value] params, + ReturnFunc rvalFunc) +{ + use commands; + use app_utils; + let str = osstream(); + outputMethod(str, name, params); + + let xml = string(str), + hash = string.hash(xml + url + name + string(theTime())), + revent = "xmlrpc-%s-return" % hash, + aevent = "xmlrpc-%s-authenticate" % hash, + eevent = "xmlrpc-%s-error" % hash; + + \: returnFunc (void; Event event) + { + //print("DEBUG: XML-RCP: Returned\n"); + for_each (event; [revent, aevent, eevent]) unbind(event); + use Value; + + removeInFlight(hash); + + Value v = ErrorValue; + + try + { + v = parseResult(event.contents()); + } + catch (exception exc) + { + print("ERROR: XML-RPC: Parsing Failed: %s\n" % exc); + print("DEBUG: Value = %s\n" % v); + print("DEBUG: XML = %s\n" % event.contents()); + } + + try + { + rvalFunc(v); + } + catch (...) { ; } + } + + \: errorFunc (void; Event event) + { + for_each (event; [revent, aevent, eevent]) unbind(event); + removeInFlight(hash); + throw exception("ERROR: XMP-RPC: method \"%s\"" % name); + } + + \: authenticateFunc (void; Event event) + { + for_each (event; [revent, aevent, eevent]) unbind(event); + throw exception("ERROR: XMP-RPC: method \"%s\" authentication not implementated" % name); + } + + bind(revent, returnFunc); + bind(aevent, authenticateFunc); + bind(eevent, errorFunc); + + addInFlight(hash); + //print("DEBUG: XML-RCP: Sending\n"); + httpPost(url, + [("Content-Type", "text/xml")], + xml, + revent, aevent, eevent); +} + +\: test (void;) +{ + use Value; + + let p1 = Struct( [ ("script_name", String("rv") ), + ("script_key", String("4b1676497a208c845b12f5c6734daf9d6e7c6274")) ] ); + + let p2 = Struct( [ ("paging", + Struct( [("current_page", Int(1)), + ("entities_per_page", Int(500))] )), + ("filters", + Struct( [("conditions", EmptyArray), + ("logical_operator", String("and"))] )), + ("type", String("Shot")) + ] ); + + \: F (void; Value v) + { + print("F = %s\n" % v); + } + + call("https://mystudio.shotgridstudio.com/api3_preview/", "read", [p1, p2], F); +} + +} // module xmlrpc diff --git a/src/lib/app/py_rvui/CMakeLists.txt b/src/lib/app/py_rvui/CMakeLists.txt new file mode 100644 index 000000000..355f5da89 --- /dev/null +++ b/src/lib/app/py_rvui/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +SET(_target + py_rvui +) + +RV_STAGE( + TARGET + ${_target} + TYPE + "PYTHON_SOURCE_MODULE" + FILES + rv + rv_commands_setup.py +) diff --git a/src/lib/app/py_rvui/rv/__init__.py b/src/lib/app/py_rvui/rv/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/lib/app/py_rvui/rv/extra_commands.py b/src/lib/app/py_rvui/rv/extra_commands.py new file mode 100644 index 000000000..3329c3736 --- /dev/null +++ b/src/lib/app/py_rvui/rv/extra_commands.py @@ -0,0 +1,7 @@ +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +def __dummy__(): + pass diff --git a/src/lib/app/py_rvui/rv/qtutils.py b/src/lib/app/py_rvui/rv/qtutils.py new file mode 100644 index 000000000..107b0994d --- /dev/null +++ b/src/lib/app/py_rvui/rv/qtutils.py @@ -0,0 +1,71 @@ +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +import rv.commands +import os + +from PySide2 import QtGui, QtWidgets +from PySide2.QtGui import * +from PySide2.QtWidgets import * +from shiboken2 import wrapInstance +from shiboken2 import getCppPointer + + +def sessionWindow(): + """ + Returns the QMainWindow for the current RV session window. + """ + + rvPyLongPtr = rv.commands.sessionWindow() + if rvPyLongPtr is not None: + return wrapInstance(rvPyLongPtr, QMainWindow) + else: + return None + + +def sessionGLView(): + """ + Returns the QOpenGLWidget for the current RV session GL view. + """ + + rvPyLongPtr = rv.commands.sessionGLView() + if rvPyLongPtr is not None: + return wrapInstance(rvPyLongPtr, QOpenGLWidget) + else: + return None + + +def sessionTopToolBar(): + """ + Returns the QToolBar for the current RV session top tool bar. + """ + + rvPyLongPtr = rv.commands.sessionTopToolBar() + if rvPyLongPtr is not None: + return wrapInstance(rvPyLongPtr, QToolBar) + else: + return None + + +def sessionBottomToolBar(): + """ + Returns the QToolBar for the current RV session bottom tool bar. + """ + + rvPyLongPtr = rv.commands.sessionBottomToolBar() + if rvPyLongPtr is not None: + return wrapInstance(rvPyLongPtr, QToolBar) + else: + return None + + +def javascriptExport(webFrame): + """ + Add the "rvsession" object to the JavaScript instance of the given + QWebFrame, connecting it to RV's event system and scripting layers. + """ + + rvPyLongPtrs = getCppPointer(webFrame) + rv.commands.javascriptExport(rvPyLongPtrs[0]) diff --git a/src/lib/app/py_rvui/rv/runtime.py b/src/lib/app/py_rvui/rv/runtime.py new file mode 100644 index 000000000..cf70b822f --- /dev/null +++ b/src/lib/app/py_rvui/rv/runtime.py @@ -0,0 +1,23 @@ +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +import os + + +def __dummy__(): + pass + + +def setup_webview_default_profile(): + default_profile = QWebEngineProfile.defaultProfile() + user_agent = ( + default_profile.httpUserAgent() + " RV/" + os.environ["TWK_APP_VERSION"] + ) + default_profile.setHttpUserAgent(user_agent) + + +from PySide2.QtWebEngineWidgets import QWebEngineProfile + +setup_webview_default_profile() diff --git a/src/lib/app/py_rvui/rv/rvtypes.py b/src/lib/app/py_rvui/rv/rvtypes.py new file mode 100644 index 000000000..a10ccee5c --- /dev/null +++ b/src/lib/app/py_rvui/rv/rvtypes.py @@ -0,0 +1,382 @@ +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +import rv.commands +import os +import sys +import math + + +def concat_seperator(): + import platform + + p = platform.system() + if p == "Windows": + return ";" + else: + return ":" + + +class State: + """The session State object for all things python""" + + def __init__(self): + self.minorModes = [] + + +class Mode(object): + """A mode is a feature unit. It comes in two varieties: major and + minor. The mode can be declared in its own file and provide key bindings, a + menu, multiple event tables if needed, a render function, and is + identifiable by name and icon. When active the minor mode can render and + its menu is visible. When inactive, the minor mode is completely gone from + RV.""" + + def __init__(self): + self._active = False + self._modeName = None + self._menu = None + self._supportFilesPath = None + self._drawOnEmpty = False + self._drawOnPresentation = False + + def isActive(self): + return self._active + + def modeName(self): + return self._modeName + + def drawOnEmpty(self): + return self._drawOnEmpty + + def drawOnPresentation(self): + return self._drawOnPresentation + + def activate(self): + "Called right after activation" + self._active = True + pass + + def deactivate(self): + "Called right before deactivation" + self._active = False + pass + + def toggle(self): + self._active = not self._active + + if self._active: + rv.commands.activateMode(self._modeName) + self.activate() + else: + self.deactivate() + rv.commands.deactivateMode(self._modeName) + + rv.commands.redraw() + rv.commands.sendInternalEvent( + "mode-toggled", "%s|%s" % (self._modeName, self._active), "Mode" + ) + + def layout(self, event): + "Layout any margins or precompute anything necessary for rendering" + pass + + def render(self, event): + "The render function is called on each active minor mode." + pass + + def supportPath(self, module, packageName=None): + if packageName == None: + packageName = module.__name__ + return os.path.join( + os.path.dirname(os.path.dirname(module.__file__)), + "SupportFiles", + packageName, + ) + + def qmlPath(self, module, packageName=None): + if packageName == None: + packageName = module.__name__ + return os.path.join( + os.path.dirname(os.path.dirname(module.__file__)), "QML", packageName + ) + + def configPath(self, packageName): + """Returns a path to a writable directory where temporary, and + configuration files specific to a package are/should be located. The + directory will be created if it does not yet exist.""" + + import platform + + envvar = os.getenv("TWK_APP_SUPPORT_PATH") + userDir = envvar.split(concat_seperator())[0] + directory = os.path.join(os.path.join(userDir, "ConfigFiles"), packageName) + + try: + if not os.path.exists(directory): + sys.mkdir(directory, 0x1FF) + except: + print("ERROR: mode config path failed to create directory %s" % directory) + + return directory + + +class MinorMode(Mode): + """MinorModes are modes which are non-exclusive: there can be many minor + modes active at the same time.""" + + def __init__(self): + Mode.__init__(self) + + def init( + self, + name, + globalBindings, + overrideBindings, + menu=None, + sortKey=None, + ordering=0, + ): + self._modeName = name + self._drawOnEmpty = False + self._drawOnPresentation = False + + rv.commands.defineMinorMode(name, sortKey, ordering) + + if globalBindings is not None: + for b in globalBindings: + (event, func, docs) = b + rv.commands.bind(self._modeName, "global", event, func, docs) + + if overrideBindings is not None: + for b in overrideBindings: + (event, func, docs) = b + rv.commands.bind(self._modeName, "global", event, func, docs) + + self.setMenu(menu) + + def setMenu(self, menu): + if menu == None: + menu = [] + self._menu = menu + rv.commands.defineModeMenu(self._modeName, self._menu, False) + + def setMenuStrict(self, menu): + if menu == None: + menu = [] + self._menu = menu + rv.commands.defineModeMenu(self._modeName, self._menu, True) + + def defineEventTable(self, tableName, bindings): + for b in bindings: + (event, func, docs) = b + rv.commands.bind(self._modeName, tableName, event, func, docs) + + def defineEventTableRegex(self, tableName, bindings): + for b in bindings: + (event, func, docs) = b + rv.commands.bindRegex(self._modeName, tableName, event, func, docs) + + def urlDropFunc(self, url): + f = None + s = None + return (f, s) + + +class Widget(MinorMode): + """The Widget class is the base class for HUD widgets. Each Widget must + provide a set of bindings, a render function (to draw itself) and an + *accurate* bounds which will determine where events are sent.""" + + def __init__(self): + MinorMode.__init__(self) + + def layout(self, event): + pass + + def render(self, event): + pass + + def init( + self, + name, + globalBindings, + overrideBindings, + menu=None, + sortKey=None, + ordering=0, + ): + self._x = 0 + self._y = 0 + self._w = 100 + self._h = 100 + self._downPoint = (0, 0) + self._dragging = False + self._inCloseArea = False + self._containsPointer = False + self._whichMargin = -1 + MinorMode.init( + self, name, globalBindings, overrideBindings, menu, sortKey, ordering + ) + + def toggle(self): + self._active = not self._active + + rv.commands.writeSettings("Tools", "show_" + self._modeName, self._active) + if self._active: + rv.commands.activateMode(self._modeName) + else: + rv.commands.deactivateMode(self._modeName) + self.updateMargins(False) + + rv.commands.redraw() + + rv.commands.sendInternalEvent( + "mode-toggled", "%s|%s" % (self._modeName, self._active), "Mode" + ) + + def updateMargins(self, activated): + # + # Below api push/pop is required because setMargins() causes + # margins-changed event to be sent which then triggers arbitrary mu + # code, which could of course allocate memory. + # + if activated: + if self._whichMargin != -1: + # + # updateMargins with activated==true should only be + # called inside a render() call, since it's only then + # that the "event device" is set. + # + m = list(rv.commands.margins()) + marginValue = self.requiredMarginValue() + + if m[self._whichMargin] < marginValue: + m[self._whichMargin] = marginValue + rv.commands.setMargins(tuple(m)) + else: + if self._whichMargin != -1: + # + # Change only the margin this mode is rendering in, but change it + # on self._allself._ devices. + # + m = [-1.0, -1.0, -1.0, -1.0] + m[self._whichMargin] = 0.0 + rv.commands.setMargins(tuple(m)) + rv.commands.setMargins(tuple(m), True) + + def updateBounds(self, minp, maxp): + self._x = minp[0] + self._y = minp[1] + self._w = maxp[0] - minp[0] + self._h = maxp[1] - minp[1] + + if rv.commands.isModeActive(self._modeName) and not self._dragging: + rv.commands.setEventTableBBox(self._modeName, "global", minp, maxp) + self.updateMargins(True) + + def contains(self, p): + if ( + p[0] >= self._x + and p[0] <= self._x + self._w + and p[1] >= self._y + and p[1] <= self._y + self._h + ): + return True + + return False + + def requiredMarginValue(self): + vs = rv.commands.viewSize() + + if self._whichMargin == -1: + # + # No margin required + # + return 0.0 + elif self._whichMargin == 0: + # + # Left margin + # + return self._x + self._w + elif self._whichMargin == 1: + # + # Right margin + # + return vs[0] - self._x + elif self._whichMargin == 2: + # + # Top margin + # + return vs[1] - self._y + elif self._whichMargin == 3: + # + # Bottom margin + # + return self._y + self._h + return 0.0 + + def drawInMargin(self, whichMargin): + self._whichMargin = whichMargin + + def storeDownPoint(self, event): + self._downPoint = event.pointer() + + def drag(self, event): + if self._whichMargin != -1: + return + + self._dragging = True + + pp = event.pointer() + dp = self._downPoint + ip = (pp[0] - dp[0], pp[1] - dp[1]) + + if not self._inCloseArea: + self._x += ip[0] + self._y += ip[1] + + self.storeDownPoint(event) + + rv.commands.redraw() + + def near(self, event): + domain = event.subDomain() + p = event.relativePointer() + tl = (0.0, domain[1]) + pc = (p[0] - tl[0], p[1] - tl[1]) + d = math.sqrt(pc[0] * pc[0] + pc[1] * pc[1]) + m = 20 + return d < m + + def move(self, event): + self._dragging = False + + near = self.near(event) + if near != self._inCloseArea: + rv.commands.redraw() + self._inCloseArea = near + + # + # If we've left the widget, don't draw the close button. + # + self._containsPointer = self.contains(event.pointer()) + if not self._containsPointer: + self._inCloseArea = False + + event.reject() + + def release(self, event, closeFunc=None): + self._dragging = False + + near = self.near(event) + if near: + if closeFunc is not None: + closeFunc() + elif self._active: + self.toggle() + self._inCloseArea = False + + self.storeDownPoint(event) diff --git a/src/lib/app/py_rvui/rv/rvui.py b/src/lib/app/py_rvui/rv/rvui.py new file mode 100644 index 000000000..e4c99eeba --- /dev/null +++ b/src/lib/app/py_rvui/rv/rvui.py @@ -0,0 +1,77 @@ +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +import rv.commands +import rv.rvtypes + + +def modeStatus(mode): + return mode._active + + +def modeDrawOnEmpty(mode): + return mode._drawOnEmpty + + +def modeDrawOnPresentation(mode): + return mode._drawOnPresentation + + +def modeName(mode): + return mode._modeName + + +def modeActive(mode): + return mode._active + + +def modeActivate(mode): + mode.activate() + + +def modeDeactivate(mode): + mode.deactivate() + + +def modeRender(mode, event): + mode.render(event) + + +def remotePyEval(event): + "python eval() event contents" + try: + val = eval(event.contents()) + if val != None: + event.setReturnContent(str(val)) + except: + import sys + + e = sys.exc_info() + event.setReturnContent("%s: %s" % (str(e[0]), str(e[1]))) + + +def remotePyExec(event): + "python exec() event contents" + exec(event.contents()) + + +def globalBindList(bindings): + for b in bindings: + (event, func, doc) = b + rv.commands.bind("default", "global", event, func, doc) + + +def defineDefaultBindings(): + bindings = [ + ("remote-pyeval", remotePyEval, "Evaluate python expression returning result"), + ("remote-pyexec", remotePyExec, "Execute python command (no return value)"), + ] + # , ("render", render, "Python render entry point")] + globalBindList(bindings) + + +def newStateObject(): + s = rv.rvtypes.State() + return s diff --git a/src/lib/app/py_rvui/rv_commands_setup.py b/src/lib/app/py_rvui/rv_commands_setup.py new file mode 100644 index 000000000..fb501651c --- /dev/null +++ b/src/lib/app/py_rvui/rv_commands_setup.py @@ -0,0 +1,477 @@ +# +# Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +import os +import sys +import rv.commands +import rv.extra_commands +import rv.runtime +from pymu import MuSymbol + +all_mu_commands = [ + "insertByteProperty", + "showConsole", + "newImageSource", + "getCurrentImageChannelNames", + "fullScreenMode", + "remoteSendDataEvent", + "remoteNetworkStatus", + "fps", + "reload", + "loadChangedFrames", + "imagesAtPixel", + "setSessionName", + "remoteContacts", + "frameEnd", + "sequenceOfFile", + "center", + "close", + "cacheDir", + "audioTextureID", + "audioTextureComplete", + "viewNode", + "getCurrentPixelValue", + "popEventTable", + "nodes", + "audioCacheMode", + "realFPS", + "encodePassword", + "bindings", + "markFrame", + "stopTimer", + "setFrame", + "stop", + "undoHistory", + "margins", + "remoteSendEvent", + "showNetworkDialog", + "setFPS", + "setOutPoint", + "nodeConnections", + "cacheMode", + "eventToCameraSpace", + "mbps", + "resetMbps", + "imageGeometryByIndex", + "isMenuBarVisible", + "newProperty", + "newNDProperty", + "setInPoint", + "narrowToRange", + "isTimerRunning", + "setMargins", + "addSources", + "updateLUT", + "narrowedFrameStart", + "saveFileDialog", + "setHardwareStereoMode", + "setNodeInputs", + "clearHistory", + "deleteProperty", + "isBuffering", + "skipped", + "frame", + "sourceAttributes", + "sourceDataAttributes", + "renderedImages", + "outPoint", + "sessionFileName", + "setSessionFileName", + "sessionName", + "openFileDialog", + "setFiltering", + "bgMethod", + "remoteConnect", + "setViewSize", + "setStringProperty", + "properties", + "redo", + "elapsedTime", + "setFloatProperty", + "readCDL", + "readLUT", + "sendInternalEvent", + "endCompoundCommand", + "activeEventTables", + "testNodeInputs", + "metaEvaluate", + "commandLineFlag", + "unbind", + "inPoint", + "setHalfProperty", + "saveSession", + "unbindRegex", + "videoState", + "setFrameStart", + "redoHistory", + "metaEvaluateClosestByType", + "playMode", + "exportCurrentFrame", + "newNode", + "startTimer", + "decodePassword", + "fileKind", + "setInc", + "sourceMediaInfo", + "cacheInfo", + "loadCount", + "getCurrentNodesOfType", + "prefTabWidget", + "frameStart", + "isMarked", + "previousViewNode", + "getHalfProperty", + "setSourceMedia", + "viewNodes", + "eval", + "setIntProperty", + "isFullScreen", + "nodesOfType", + "sessionFromUrl", + "nodeGroup", + "nodeGroupRoot", + "isRealtime", + "isCurrentFrameIncomplete", + "redoPathSwapVars", + "sourceDisplayChannelNames", + "sourcesRendered", + "bindingDocumentation", + "setDriverAttribute", + "isCaching", + "getCurrentImageSize", + "putUrlOnClipboard", + "eventToImageSpace", + "loadTotal", + "myNetworkPort", + "presentationMode", + "httpPost", + "setSessionType", + "insertFloatProperty", + "inputAtPixel", + "setRealtime", + "deleteNode", + "ioFormats", + "getStringProperty", + "nextViewNode", + "setCursor", + "setRendererType", + "narrowedFrameEnd", + "contentAspect", + "sourcesAtFrame", + "propertyExists", + "getFiltering", + "theTime", + "getIntProperty", + "insertStringProperty", + "releaseAllCachedImages", + "getFloatProperty", + "sources", + "toggleMenuBar", + "sourceNameWithoutFrame", + "queryDriverAttribute", + "nodesInGroup", + "ioParameters", + "propertyInfo", + "setPresentationMode", + "optionsPlay", + "addSource", + "addSourceBegin", + "addSourceEnd", + "addSourceVerbose", + "addSourcesVerbose", + "setProgressiveSourceLoading", + "progressiveSourceLoading", + "setBGMethod", + "activeModes", + "optionsNoPackages", + "isCurrentFrameError", + "alertPanel", + "stereoSupported", + "nodeImageGeometry", + "nodeRangeInfo", + "resizeFit", + "startupResize", + "mapPropertyToGlobalFrames", + "isModeActive", + "setFrameEnd", + "play", + "newSession", + "getSessionType", # "javascriptMuExport", + "sessionNames", + "sourceMedia", + "setAudioCacheMode", + "exportCurrentSourceFrame", + "setEventTableBBox", + "optionsProgressiveLoading", + "getRendererType", + "pushEventTable", + "networkAccessManager", + "remoteNetwork", + "isConsoleVisible", + "undo", + "openUrl", + "openMediaFileDialog", + "insertHalfProperty", + "newImageSourcePixels", + "remoteDisconnect", + "undoPathSwapVars", + "deactivateMode", + "remoteSendMessage", + "redraw", + "insertIntProperty", + "isPlaying", + "imageToEventSpace", + "scrubAudio", + "defineMinorMode", + "cacheSize", + "addToSource", + "activateMode", + "remoteApplications", + "markedFrames", + "beginCompoundCommand", + "setCacheMode", + "sourcePixelValue", + "setByteProperty", + "inc", + "getCurrentAttributes", + "httpGet", + "closestNodesOfType", + "remoteConnections", + "viewSize", + "watchFile", + "remoteLocalContactName", + "setRemoteLocalContactName", + "remoteDefaultPermission", + "setRemoteDefaultPermission", + "flushCachedNode", + "nodeExists", + "relocateSource", + "contractSequences", + "existingFilesInSequence", + "existingFramesInSequence", + "setViewNode", + "imageGeometry", + "clearAllButFrame", + "setWindowTitle", + "nodeType", + "getByteProperty", + "sourceAtPixel", + "clearSession", + "setPlayMode", + "myNetworkHost", + "sourceMediaInfoList", + "packageListFromSetting", + "shortAppName", + "showTopViewToolbar", + "showBottomViewToolbar", + "isTopViewToolbarVisible", + "isBottomViewToolbarVisible", + "currentFrameStatus", + "editNodeSource", + "nodeTypes", + "updateNodeDefinition_", + "writeAllNodeDefinitions", + "writeNodeDefinition", + "videoDeviceIDString", + "readProfile", + "writeProfile", + "editProfiles", + "spoofConnectionStream", + "sourceGeometry", + "ocioUpdateConfig", + "cacheOutsideRegion", + "setCacheOutsideRegion", + "licensingState", + "releaseAllUnusedImages", + "launchTLI", + "validateShotgunToken", + "rvioSetup", + "imageGeometryByTag", + "hopProfDynName", + "logMetrics", + "logMetricsWithProperties", + "getVersion", + "getReleaseVariant", + "isDebug", + "setFilterLiveReviewEvents", + "filterLiveReviewEvents", + "crash", + "addSourceMediaRep", + "setActiveSourceMediaRep", + "sourceMediaRep", + "sourceMediaReps", + "sourceMediaRepSwitchNode", + "sourceMediaRepSourceNode", +] + + +all_extra_commands = [ + "popInputToTop", + "stepBackward10", + "reloadInOut", + "loadCurrentSourcesChangedFrames", + "nodesInEvalPath", + "setUIName", + "displayFeedback2", + "inputNodeUserNameAtFrame", + "sourceMetaInfoAtFrame", + "sourceImageStructure", + "stepBackward100", + "setTranslation", + "stepForward100", + "isPlayingForwards", + "setInactiveState", + "stepBackward", + "activatePackageModeEntry", + "isViewNode", + "toggleFilter", + "topLevelGroup", + "deactivatePackageModeEntry", + "set", + "associatedNode", + "cycleNodeInputs", + "toggleFullScreen", + "currentImageAspect", + "frameImage", + "activateSync", + "cacheUsage", + "toggleRealtime", + "toggleMotionScope", + "findAnnotatedFrames", + "isNarrowed", + "isPlayable", + "isPlayingBackwards", + "scale", + "toggleMotionScopeFromState", + "setScale", + "recordPixelInfo", + "setActiveState", + "displayFeedback", + "togglePlay", + "sequenceBoundaries", + "togglePlayIfNoScrub", + "isSessionEmpty", + "togglePlayVerbose", + "associatedNodes", + "toggleForwardsBackwards", + "translation", + "stepForward1", + "stepForward10", + "sourceFrame", + "nodesUnderPointer", + "centerResizeFit", + "numFrames", + "toggleSync", + "stepBackward1", + "uiName", + "stepForward", + "cprop", + "nodesInGroupOfType", + "appendToProp", + "removeFromProp", + "existsInProp", + "associatedVideoDevice", + "updatePixelInfo", + "setDisplayProfilesFromSettings", + "minorModeIsLoaded", +] + +## +## This is a stop-gap, since I can't figure out a sensible way to find +## these values programmatically. +## +## There is no sensible way! +## + +commands_int_constants = [ + ("OneFileName", 3), + ("EDLFileKind", 7), + ("UncheckedMenuState", 1), + ("StackSession", 1), + ("CursorArrow", 0), + ("ShortType", 7), + ("WarningAlert", 1), + ("ImageFileKind", 1), + ("OneDirectory", 4), + ("PlayPingPong", 2), + ("NeutralMenuState", 0), + ("ManyExistingFilesAndDirectories", 2), + ("CacheBuffer", 1), + ("ManyExistingFiles", 1), + ("HalfType", 5), + ("RGB709", 0), + ("StringType", 8), + ("MixedStateMenuState", 3), + ("NetworkStatusOn", 1), + ("FloatType", 1), + ("NetworkStatusOff", 0), + ("CacheOff", 0), + ("ErrorAlert", 2), + ("IntType", 2), + ("PlayLoop", 0), + ("CursorDefault", 0), + ("CDLFileKind", 3), + ("LUTFileKind", 4), + ("MovieFileKind", 2), + ("PlayOnce", 1), + ("RVFileKind", 6), + ("CursorNone", 10), + ("InfoAlert", 0), + ("SequenceSession", 0), + ("DisabledMenuState", -1), + ("CacheGreedy", 2), + ("DirectoryFileKind", 5), + ("UnknownFileKind", 0), + ("ByteType", 6), + ("CIEXYZ", 1), + ("CheckedMenuState", 2), + ("OneExistingFile", 0), + ("IndependentDisplayMode", 0), + ("MirrorDisplayMode", 1), + ("NotADisplayMode", 2), + ("VideoAndDataFormatID", 1), + ("DeviceNameID", 4), + ("ModuleNameID", 5), + ("NetworkPermissionAsk", 0), + ("NetworkPermissionAllow", 1), + ("NetworkPermissionDeny", 2), +] + + +def bind_symbols(symbol_list, module_name, mod): + for sym in symbol_list: + try: + s = MuSymbol(module_name + "." + sym) + setattr(mod, sym, s) + except: + print("Bind to python failed:", sym) + + +def bind_constants(constant_list, mod): + for const_pair in constant_list: + try: + setattr(mod, const_pair[0], const_pair[1]) + except: + print("Bind to python failed:", sym) + + +bind_symbols(all_mu_commands, "commands", rv.commands) +bind_symbols(all_extra_commands, "extra_commands", rv.extra_commands) +bind_symbols(["eval"], "runtime", rv.runtime) +bind_constants(commands_int_constants, rv.commands) + +rv.extra_commands.displayFeedback = rv.extra_commands.displayFeedback2 + +if "RV_NO_CONSOLE_REDIRECT" not in os.environ: + + class RVStdOut: + def __init__(self): + self.write = MuSymbol("extra_commands._print") + + def flush(self): + pass + + sys.stdout = RVStdOut() + sys.stderr = sys.stdout diff --git a/src/lib/audio/ALSAAudioModule/ALSAAudioModule/ALSAAudioRenderer.h b/src/lib/audio/ALSAAudioModule/ALSAAudioModule/ALSAAudioRenderer.h new file mode 100644 index 000000000..2d0812430 --- /dev/null +++ b/src/lib/audio/ALSAAudioModule/ALSAAudioModule/ALSAAudioRenderer.h @@ -0,0 +1,101 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifndef __IPCore__ALSAAudioRenderer__h__ +#define __IPCore__ALSAAudioRenderer__h__ +#include +#include +#include +#include + +#include + +namespace IPCore { + +/// Pure ALSA implementation of AudioRenderer + +/// +/// +/// + +class ALSAAudioRenderer : public AudioRenderer +{ +public: + // + // Types + // + + struct ALSADevice + { + std::string plug; + std::string card; + std::string name; + }; + + typedef std::vector ALSADeviceVector; + typedef TwkAudio::AudioBuffer AudioBuffer; + typedef stl_ext::thread_group ThreadGroup; + typedef std::vector OutputSampleBuffer; + typedef TwkUtil::Timer Timer; + + ALSAAudioRenderer(const RendererParameters&); + ~ALSAAudioRenderer(); + + + /// + /// play() will return almost immediately -- a worker thread will + /// be released and start playing. + /// + + virtual void play(); + virtual void play(Session*); + + /// + /// stop() will cause the worker thread to wait until play. A + /// subsequent play should restart it. + /// + + virtual void stop(Session*); + + /// + /// Completely stop the audio hardware and the audio thread. You can't + /// call play() after you've called shutdown() + /// + + virtual void shutdown(); + + /// + /// These are currently ignored by ALSAAudioRenderer + /// + + virtual void availableLayouts(const Device&, LayoutsVector&); + virtual void availableFormats(const Device&, FormatVector&); + virtual void availableRates(const Device&, Format, RateVector&); + + void threadMain(); + void configureDevice(); + +protected: + virtual void createDeviceList(); + +private: + OutputSampleBuffer m_outBuffer; + ALSADeviceVector m_alsaDevices; + ThreadGroup m_threadGroup; + size_t m_startSample; + snd_pcm_t* m_pcm; + snd_pcm_uframes_t m_periodSize; + snd_pcm_uframes_t m_bufferSize; + pthread_t m_audioThread; + bool m_audioThreadRunning; + pthread_mutex_t m_runningLock; +}; + + +} // Rv + +#endif // __IPCore__ALSAAudioRenderer__h__ diff --git a/src/lib/audio/ALSAAudioModule/ALSAAudioRenderer.cpp b/src/lib/audio/ALSAAudioModule/ALSAAudioRenderer.cpp new file mode 100644 index 000000000..04b7e4183 --- /dev/null +++ b/src/lib/audio/ALSAAudioModule/ALSAAudioRenderer.cpp @@ -0,0 +1,1232 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// +// "Safe" ALSA is some unknown subset of the ALSA API that works +// under more conditions than the "not safe". This API is refered to +// by a PulseAudio developer here: +// +// http://0pointer.de/blog/projects/guide-to-sound-apis.html +// +// Here's the important excerpt: +// +// You want to know more about the safe ALSA subset? +// +// Here's a list of DOS and DONTS in the ALSA API if you care about +// that you application stays future-proof and works fine with +// non-hardware backends or backends for user-space sound drivers +// such as Bluetooth and FireWire audio. Some of these +// recommendations apply for people using the full ALSA API as +// well, since some functionality should be considered obsolete for +// all cases. +// +// If your application's code does not follow these rules, you must +// have a very good reason for that. Otherwise your code should +// simply be considered broken! +// +// DONTS: +// +// * Do not use "async handlers", e.g. via +// snd_async_add_pcm_handler() and friends. Asynchronous +// handlers are implemented using POSIX signals, which is a +// very questionable use of them, especially from libraries +// and plugins. Even when you don't want to limit yourself to +// the safe ALSA subset it is highly recommended not to use +// this functionality. Read this for a longer explanation why +// signals for audio IO are evil. +// +// * Do not parse the ALSA configuration file yourself or with +// any of the ALSA functions such as snd_config_xxx(). If you +// need to enumerate audio devices use snd_device_name_hint() +// (and related functions). That is the only API that also +// supports enumerating non-hardware audio devices and audio +// devices with drivers implemented in userspace. +// +// * Do not parse any of the files from /proc/asound/. Those +// files only include information about kernel sound drivers +// -- user-space plugins are not listed there. Also, the set +// of kernel devices might differ from the way they are +// presented in user-space. (i.e. sub-devices are mapped in +// different ways to actual user-space devices such as +// surround51 an suchlike. +// +// * Do not rely on stable device indexes from ALSA. Nowadays +// they depend on the initialization order of the drivers +// during boot-up time and are thus not stable. +// +// * Do not use the snd_card_xxx() APIs. For enumerating use +// snd_device_name_hint() (and related +// functions). snd_card_xxx() is obsolete. It will only list +// kernel hardware devices. User-space devices such as sound +// servers, Bluetooth audio are not included. snd_card_load() +// is completely obsolete in these days. +// +// * Do not hard-code device strings, especially not hw:0 or +// plughw:0 or even dmix -- these devices define no channel +// mapping and are mapped to raw kernel devices. It is highly +// recommended to use exclusively default as device +// string. If specific channel mappings are required the +// correct device strings should be front for stereo, +// surround40 for Surround 4.0, surround41, surround51, and +// so on. Unfortunately at this point ALSA does not define +// standard device names with channel mappings for non-kernel +// devices. This means default may only be used safely for +// mono and stereo streams. You should probably prefix your +// device string with plug: to make sure ALSA transparently +// reformats/remaps/resamples your PCM stream for you if the +// hardware/backend does not support your sampling parameters +// natively. +// +// * Do not assume that any particular sample type is supported +// except the following ones: U8, S16_LE, S16_BE, S32_LE, +// S32_BE, FLOAT_LE, FLOAT_BE, MU_LAW, A_LAW. +// +// * Do not use snd_pcm_avail_update() for synchronization +// purposes. It should be used exclusively to query the +// amount of bytes that may be written/read right now. Do not +// use snd_pcm_delay() to query the fill level of your +// playback buffer. It should be used exclusively for +// synchronisation purposes. Make sure you fully understand +// the difference, and note that the two functions return +// values that are not necessarily directly connected! +// +// * Do not assume that the mixer controls always know dB +// information. +// +// * Do not assume that all devices support MMAP style buffer +// access. +// +// * Do not assume that the hardware pointer inside the +// (possibly mmaped) playback buffer is the actual position +// of the sample in the DAC. There might be an extra latency +// involved. +// +// * Do not try to recover with your own code from ALSA error +// conditions such as buffer under-runs. Use +// snd_pcm_recover() instead. +// +// * Do not touch buffering/period metrics unless you have +// specific latency needs. Develop defensively, handling +// correctly the case when the backend cannot fulfill your +// buffering metrics requests. Be aware that the buffering +// metrics of the playback buffer only indirectly influence +// the overall latency in many cases. i.e. setting the buffer +// size to a fixed value might actually result in practical +// latencies that are much higher. +// +// * Do not assume that snd_pcm_rewind() is available and works +// and to which degree. +// +// * Do not assume that the time when a PCM stream can receive +// new data is strictly dependant on the sampling and +// buffering parameters and the resulting average +// throughput. Always make sure to supply new audio data to +// the device when it asks for it by signalling "writability" +// on the fd. (And similarly for capturing) +// +// * Do not use the "simple" interface snd_spcm_xxx(). +// +// * Do not use any of the functions marked as "obsolete". +// +// * Do not use the timer, midi, rawmidi, hwdep subsystems. +// +// DOS: +// +// * Use snd_device_name_hint() for enumerating audio devices. +// +// * Use snd_smixer_xx() instead of raw snd_ctl_xxx() +// +// * For synchronization purposes use snd_pcm_delay(). +// +// * For checking buffer playback/capture fill level use +// snd_pcm_update_avail(). +// +// * Use snd_pcm_recover() to recover from errors returned by any +// of the ALSA functions. +// +// * If possible use the largest buffer sizes the device +// supports to maximize power saving and drop-out safety. Use +// snd_pcm_rewind() if you need to react to user input +// quickly. +// + +//#define USE_SAFE_ALSA + +#ifndef USE_SAFE_ALSA +#define ALSA_PCM_NEW_HW_PARAMS_API +#define ALSA_PCM_NEW_SW_PARAMS_API +#endif + +#include + +namespace IPCore { +using namespace std; +using namespace stl_ext; +using namespace TwkApp; +using namespace TwkAudio; + +static void +alsaErrorHandler (const char *file, + int line, + const char *function, + int err, + const char *fmt, + ...) +{ + // don't print out ALSA asserts +} + +static int +snd_pcm_open_real_hard(snd_pcm_t **pcm, + const char *name, + snd_pcm_stream_t stream, + int mode, + size_t iterations = 1) +{ + // + // Try REAL HARD to open the device. If it doesn't open right + // away. Wait a second and see if anything improves. There's + // probably a way to avoid this by waiting until the device is + // drained, but one might assume snd_pcm_drain() would do that + // wouldn't one? + // + // It seems like the device is still playing back accumulated + // samples and cannot be opened (e.g., switching to an exclusive + // hardware device from default). If you wait for the device to + // drain, it will eventually work. This technique (or a + // variation) is applied by a number of other programs on the + // net. + // + + int err = snd_pcm_open(pcm, name, stream, mode); + +#if 0 + for (size_t i = 0; i < iterations && err == -EBUSY; i++) + { + usleep(10000); + err = snd_pcm_open(pcm, name, stream, mode); + } +#endif + + if (!err && *pcm) snd_pcm_nonblock(*pcm, 0); // 0 means block + return err; +} + +ALSAAudioRenderer::ALSAAudioRenderer(const RendererParameters& p) + : AudioRenderer(p), + m_threadGroup(1,2), + m_pcm(0), + m_audioThread(pthread_self()), + m_audioThreadRunning(false) +{ + snd_lib_error_set_handler(alsaErrorHandler); + pthread_mutex_init(&m_runningLock, 0); + + if (m_parameters.rate == 0) m_parameters.rate = TWEAK_AUDIO_DEFAULT_SAMPLE_RATE; + if (m_parameters.framesPerBuffer == 0) m_parameters.framesPerBuffer = 512; + if (m_parameters.device == "") m_parameters.device = "default"; + + createDeviceList(); + + if (debug) outputParameters(m_parameters); + + #ifndef PLATFORM_WINDOWS + /* + We used to initialize the device at this point, but if something + goes wrong, it means we can't even change the device in the + prefs, because audio is shutdown. If we skip this step, we'll + hold open the device after the first play(). + */ + + if (m_parameters.holdOpen) + { + play(); + AudioRenderer::stop(); + } + #endif +} + +ALSAAudioRenderer::~ALSAAudioRenderer() +{ + AudioRenderer::stop(); + shutdown(); + pthread_mutex_destroy(&m_runningLock); +} + +void +ALSAAudioRenderer::createDeviceList() +{ + m_outputDevices.clear(); + m_alsaDevices.clear(); + +#ifdef USE_SAFE_ALSA + // + // use the snd_device_name_hint() function + // + + void** deviceList = 0; + snd_device_name_hint(-1, "pcm", (void***)&deviceList); + + for (void** d = deviceList; *d; d++) + { + char* namep = snd_device_name_get_hint(*d, "NAME"); + char* descp = snd_device_name_get_hint(*d, "DESC"); + const string name = namep; + const string desc = descp; + + // + // we only do stereo right now + // + if (name == "null" || name.find("surround") != string::npos) continue; + + string::size_type n = desc.find('\n'); + const string desc0 = desc.substr(0, n); + const string desc1 = desc.substr(n+1, desc.size()); + + free(namep); + free(descp); + + Device d(name); + d.layout = 0; + d.defaultRate = 0; + d.isDefaultDevice = false; + d.index = m_outputDevices.size(); + + ALSADevice ad; + ad.plug = "plugIn"; + ad.name = name; + ad.card = ""; + + m_outputDevices.push_back(d); + m_alsaDevices.push_back(ad); + } + + snd_device_name_free_hint(deviceList); + +#else + + for (int card = -1; snd_card_next(&card) >= 0 && card >= 0;) + { + // + // Find the card devices + // + + snd_ctl_t* ctl = 0; + char* cardname = 0; + ostringstream hwcard; + hwcard << "hw:" << card; + + snd_card_get_longname(card, &cardname); + + if (snd_ctl_open(&ctl, hwcard.str().c_str(), 0) == 0) + { + snd_pcm_info_t* info = 0; + snd_pcm_info_alloca(&info); + + for (int device = -1; snd_ctl_pcm_next_device(ctl, &device) >= 0 && device >= 0;) + { + snd_pcm_info_set_device(info, device); + snd_pcm_info_set_subdevice(info, 0); + snd_pcm_info_set_stream(info, SND_PCM_STREAM_PLAYBACK); + + if (snd_ctl_pcm_info(ctl, info) >= 0) + { + ostringstream fullname; + + fullname << cardname + << ": " << snd_pcm_info_get_name(info) + << " (" << hwcard.str() + << "," << device + << ")"; + + ostringstream dname; + dname << hwcard.str() << "," << device; + + Device d(fullname.str()); + d.layout = TwkAudio::UnknownLayout; + d.defaultRate = 0; + d.isDefaultDevice = false; + d.index = m_outputDevices.size(); + + char* c; + snd_card_get_name(card, &c); + + ALSADevice ad; + ad.plug = "hw"; + ad.name = dname.str(); + ad.card = c; + + m_outputDevices.push_back(d); + m_alsaDevices.push_back(ad); + } + } + + snd_ctl_close(ctl); + } + } + +#endif + + // + // See if RV_ALSA_EXTRA_DEVICES is set and if so scarf the + // devices out of there too. + // + + if (const char* xdevices = getenv("RV_ALSA_EXTRA_DEVICES")) + { + vector tokens; + stl_ext::tokenize(tokens, xdevices, "|"); + + for (size_t i = 0; i < tokens.size(); i++) + { + vector dtokens; + stl_ext::tokenize(dtokens, tokens[i], "@"); + + string name; + string alsaDevice; + + if (dtokens.size() == 1) + { + name = tokens[i]; + alsaDevice = name; + } + else if (dtokens.size() == 2) + { + name = dtokens[0]; + alsaDevice = dtokens[1]; + } + else + { + cerr << "ERROR: RV_ALSA_EXTRA_DEVICES env variable -- syntax error" << endl; + break; + } + + Device d(name); + d.layout = TwkAudio::Stereo_2; + d.defaultRate = TWEAK_AUDIO_DEFAULT_SAMPLE_RATE; + d.latencyLow = 0; + d.latencyHigh = 0; + d.isDefaultDevice = false; + d.index = m_outputDevices.size(); + + ALSADevice ad; + ad.plug = "plughw"; + ad.card = "PlugIn Module"; + ad.name = alsaDevice; + + m_outputDevices.push_back(d); + m_alsaDevices.push_back(ad); + } + } + + // + // Add default + // + + Device d("default"); + d.layout = TwkAudio::Stereo_2; + d.defaultRate = TWEAK_AUDIO_DEFAULT_SAMPLE_RATE; + d.latencyLow = 0; + d.latencyHigh = 0; + d.isDefaultDevice = true; + d.index = m_outputDevices.size(); + + ALSADevice ad; + ad.plug = "plughw"; + ad.card = "PlugIn Module"; + ad.name = "default"; + + m_outputDevices.push_back(d); + m_alsaDevices.push_back(ad); + + // + // Copy the current state from the parameters + // + + DeviceState state; + state.device = m_parameters.device; + state.format = m_parameters.format; + state.rate = m_parameters.rate; + state.latency = m_parameters.latency; + state.layout = m_parameters.layout; + state.framesPerBuffer = m_parameters.framesPerBuffer; + setDeviceState(state); + + // + // Force allocation of memory now + // + + int channelsCount = TwkAudio::channelsCount(state.layout); + m_abuffer.reconfigure(state.framesPerBuffer, state.layout, Time(state.rate), 0, 0); + m_outBuffer.resize(m_abuffer.size() * channelsCount * formatSizeInBytes(state.format)); +} + + +void +ALSAAudioRenderer::availableLayouts(const Device& d, LayoutsVector& layouts) +{ + layouts.clear(); + + layouts.push_back(TwkAudio::Mono_1); + layouts.push_back(TwkAudio::Stereo_2); +} + + +void +ALSAAudioRenderer::availableFormats(const Device& d, FormatVector& formats) +{ + // + // The base class ensures that this function is not called when a + // device is opened. + // + + formats.clear(); + static Format allformats[4] = { Float32Format, Int32Format, Int16Format, Int8Format }; + + const ALSADevice& ad = m_alsaDevices[d.index]; + const string& deviceName = ad.name; + + for (size_t i = 0; i < 3; i++) + { + Format format = allformats[i]; + + snd_pcm_t* pcm; + int err; + + if ((err = snd_pcm_open_real_hard(&pcm, + deviceName.c_str(), + SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK, + 10)) == 0) + { + snd_pcm_hw_params_t* params; + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(pcm, params); + + unsigned int rrate = (unsigned int)m_parameters.rate; + + snd_pcm_format_t alsaformat; + + switch (format) + { + case Float32Format: alsaformat = SND_PCM_FORMAT_FLOAT_LE; break; + case Int32Format: alsaformat = SND_PCM_FORMAT_S32_LE; break; + case Int16Format: alsaformat = SND_PCM_FORMAT_S16_LE; break; + case Int8Format: alsaformat = SND_PCM_FORMAT_S8; break; + } + + int dir = 0; + snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); + bool ok = snd_pcm_hw_params_set_format(pcm, params, alsaformat) == 0; + snd_pcm_hw_params_set_channels(pcm, params, TwkAudio::channelsCount(m_parameters.layout)); + snd_pcm_hw_params_set_rate_near(pcm, params, &rrate, &dir); + + if (ok && rrate == m_parameters.rate) + { + int periods = 2; + snd_pcm_uframes_t period_size = 8192; + snd_pcm_uframes_t buffer_size = 0; + + snd_pcm_hw_params_set_periods(pcm, params, periods, 0); + snd_pcm_hw_params_set_buffer_size(pcm, params, (period_size * periods) >> 2); + + if (snd_pcm_hw_params(pcm, params) >= 0) + { + formats.push_back(format); + } + } + + snd_pcm_close(pcm); + } + else if (err == -EBUSY) + { + // + // The device isn't available + // + + break; + } + } +} + +void +ALSAAudioRenderer::availableRates(const Device& device, Format format, RateVector& rates) +{ + // + // The base class ensures that this function is not called when a + // device is opened. + // + + rates.clear(); + + static double standardSampleRates[] = { + 8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0, + 44100.0, 48000.0, 88200.0, 96000.0, 192000.0, -1 /* negative terminated list */ + }; + + const ALSADevice& ad = m_alsaDevices[device.index]; + const string& deviceName = ad.name; + + for (size_t i = 0; standardSampleRates[i] > 0; i++) + { + double rate = standardSampleRates[i]; + snd_pcm_t* pcm; + int err; + + if ((err = snd_pcm_open_real_hard(&pcm, + deviceName.c_str(), + SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK, + 10)) == 0) + { + snd_pcm_hw_params_t* params; + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(pcm, params); + + unsigned int rrate = (unsigned int)rate; + + snd_pcm_format_t alsaformat; + + switch (m_parameters.format) + { + case Float32Format: alsaformat = SND_PCM_FORMAT_FLOAT_LE; break; + case Int32Format: alsaformat = SND_PCM_FORMAT_S32_LE; break; + case Int16Format: alsaformat = SND_PCM_FORMAT_S16_LE; break; + case Int8Format: alsaformat = SND_PCM_FORMAT_S8; break; + } + + // + // This doesn't work -- why does ALSA say it does? + // + + int dir = 0; + snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(pcm, params, alsaformat); + snd_pcm_hw_params_set_channels(pcm, params, TwkAudio::channelsCount(m_parameters.layout)); + snd_pcm_hw_params_set_rate_near(pcm, params, &rrate, &dir); + + if (rrate == rate) rates.push_back((unsigned int)rate); + +#if 0 + int periods = 2; + snd_pcm_uframes_t period_size = 8192; + snd_pcm_uframes_t buffer_size = 0; + + snd_pcm_hw_params_set_periods(pcm, params, periods, 0); + snd_pcm_hw_params_set_buffer_size(pcm, params, (period_size * periods) >> 2); + + if (snd_pcm_hw_params(pcm, params) >= 0 && rrate == rate) + { + rates.push_back((unsigned int)rate); + } +#endif + + snd_pcm_close(pcm); + } + } +} + +static void +trampoline(void* data) +{ + ALSAAudioRenderer* a = (ALSAAudioRenderer*)data; + a->threadMain(); +} + +void +ALSAAudioRenderer::configureDevice() +{ + int err; + + if (!m_pcm) + { + string alsaDeviceName = "default"; + int dindex = findDeviceByName(m_parameters.device); + + if (dindex >= 0 && dindex < m_alsaDevices.size()) + { + const ALSADevice& ad = m_alsaDevices[dindex]; + alsaDeviceName = ad.name; + } + + if ((err = snd_pcm_open_real_hard(&m_pcm, + alsaDeviceName.c_str(), + SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK)) < 0) + { + cerr << "WARNING: ALSA: Playback open error (" + << err << "): " << snd_strerror(err) << endl; + + + if (alsaDeviceName != "default" && err != -16) /* busy */ + { + cerr << "WARNING: ALSA: trying default instead of " + << alsaDeviceName + << endl; + + if ((err = snd_pcm_open_real_hard(&m_pcm, + "default", + SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK)) < 0) + { + cerr << "WARNING: ALSA: no luck with default either" << endl; + setErrorCondition("Unable to open an audio device"); + } + else + { + cerr << "WARNING: ALSA: using default device instead" << endl; + m_deviceState.device = "default"; + alsaDeviceName = "default"; + } + } + else + { + setErrorCondition("Unable to open an audio device"); + } + } + + snd_pcm_hw_params_t* params; + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(m_pcm, params); + + snd_pcm_format_t alsaformat; + size_t dataSize = 0; + + switch (m_parameters.format) + { + case Float32Format: + alsaformat = SND_PCM_FORMAT_FLOAT_LE; + dataSize = sizeof(float); + break; + default: + case Int16Format: + alsaformat = SND_PCM_FORMAT_S16_LE; + dataSize = sizeof(short); + break; + case Int32Format: + alsaformat = SND_PCM_FORMAT_S32_LE; + dataSize = sizeof(signed int); + break; + case Int8Format: + alsaformat = SND_PCM_FORMAT_S8; + dataSize = sizeof(char); + break; + } + + unsigned int rrate = (unsigned int)m_parameters.rate; + int dir = 0; + + if (snd_pcm_hw_params_set_access(m_pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) + { + cerr << "ERROR: ALSA: access (interleaved) failed - can't continue" << endl; + outputParameters(m_parameters); + snd_pcm_close(m_pcm); + m_pcm = 0; + setErrorCondition("unable to configure audio device"); + } + + if (snd_pcm_hw_params_set_format(m_pcm, params, alsaformat) < 0) + { + if (snd_pcm_hw_params_set_format(m_pcm, params, SND_PCM_FORMAT_S16_LE) >= 0) + { + if (debug) cerr << "WARNING: ALSA: format falling back to Int16" << endl; + m_parameters.format = Int16Format; + } + else + { + cerr << "ERROR: ALSA: format failed - can't continue" << endl; + outputParameters(m_parameters); + snd_pcm_close(m_pcm); + m_pcm = 0; + setErrorCondition("unable to configure audio device"); + } + } + + int channelsCount = TwkAudio::channelsCount(m_parameters.layout); + + if (snd_pcm_hw_params_set_channels(m_pcm, params, channelsCount) < 0) + { + cerr << "ERROR: ALSA: can't use " << channelsCount + << " channels -- can't continue" << endl; + outputParameters(m_parameters); + snd_pcm_close(m_pcm); + m_pcm = 0; + setErrorCondition("unable to configure audio device"); + } + + if (snd_pcm_hw_params_set_rate_near(m_pcm, params, &rrate, &dir) < 0) + { + cerr << "ERROR: ALSA: unable to set rate near " << rrate + << " -- can't continue" << endl; + outputParameters(m_parameters); + snd_pcm_close(m_pcm); + m_pcm = 0; + setErrorCondition("unable to configure audio device"); + } + + snd_pcm_hw_params_set_periods_integer(m_pcm, params); + + // + // Reasonably low latency + // + + unsigned int periods = 2; + m_periodSize = snd_pcm_uframes_t(double(512) / double(48000.0) * double(m_parameters.rate)); + m_bufferSize = m_periodSize * periods * channelsCount * dataSize; + dir = 0; + + snd_pcm_hw_params_set_periods(m_pcm, params, periods, 0); + snd_pcm_hw_params_set_period_size_near(m_pcm, params, &m_periodSize, &dir); + snd_pcm_hw_params_set_buffer_size_near(m_pcm, params, &m_bufferSize); + + if ((err = snd_pcm_hw_params(m_pcm, params)) < 0) + { + cerr << "ERROR: ALSA: params failed: " << snd_strerror(err) << endl; + snd_pcm_close(m_pcm); + m_pcm = 0; + setErrorCondition("unable to configure audio device"); + } + + snd_pcm_hw_params_get_buffer_size(params, &m_bufferSize); + snd_pcm_hw_params_get_period_size(params, &m_periodSize, &dir); + periods = m_bufferSize / m_periodSize; + + // LATENCY: + // + // latency = periodsize * periods / (rate * bytes_per_frame) + // + // is this even correct? Some sources claim there are still + // other latencies beyond this. I really don't want to add an + // extra latency UI. + // + + DeviceState nstate; + nstate.device = m_parameters.device; + nstate.format = m_parameters.format; + nstate.rate = rrate; + nstate.layout = m_parameters.layout; + nstate.latency = m_parameters.latency + m_periodSize * periods / (nstate.rate * sizeof(short) * channelsCount); + // THIS WILL BLOW IT UP IF ITS NOT 512 + //nstate.framesPerBuffer = 2048; + nstate.framesPerBuffer = m_parameters.framesPerBuffer; + setDeviceState(nstate); + + if (debug) + { + cout << "DEBUG: alsa buffer_size = " << m_bufferSize << endl + << "DEBUG: alsa period_size = " << m_periodSize << endl + << "DEBUG: alsa periods = " << periods << endl + << "DEBUG: alsa device latency = " << nstate.latency << endl; + } + + // + // Wait for the device to be ready + // + + snd_pcm_wait(m_pcm, -1); + } +} + + +void +ALSAAudioRenderer::threadMain() +{ + // + // Set the thread priority if asked to do so + // + + size_t sched_priority = Application::optionValue("displayPriority", size_t(-1)); + string schedulePolicy = Application::optionValue("schedulePolicy", ""); + + if (schedulePolicy != "") + { + sched_param sp; + memset(&sp, 0, sizeof(sched_param)); + //sp.sched_priority = opts.displayPriority; + sp.sched_priority = sched_priority; + + unsigned int policy = SCHED_OTHER; + + if (schedulePolicy == "SCHED_RR") policy = SCHED_RR; + else if (schedulePolicy == "SCHED_FIFO") policy = SCHED_FIFO; + + if (policy != SCHED_OTHER) + { + if (sched_setscheduler(0, policy, &sp)) + { + cout << "ERROR: can't set thread priority" << endl; + } + } + else + { + if (setpriority(PRIO_PROCESS, 0, sp.sched_priority)) + { + cout << "ERROR: can't set thread priority" << endl; + } + } + } + + int err; + + // + // NOTE: it's possible for this thread to continue on as a + // zombie (after the control thread tried to stop it). In that + // case m_pcm will be null, so check for that whenever we're + // going to use it. This should stop the "asset failed: pcm" + // crashes we get from alsa. + // + if (!m_pcm) return; + + m_threadGroup.lock(m_runningLock); + m_audioThreadRunning = true; + m_audioThread = pthread_self(); + m_threadGroup.unlock(m_runningLock); + + // + // Get the current state. (yeah, I know it was just set above) + // + + const DeviceState& state = deviceState(); + + // + // Setup accumulation buffer + // + + int channelsCount = TwkAudio::channelsCount(state.layout); + + AudioBuffer buffer(state.framesPerBuffer, + state.layout, + state.rate); + + m_abuffer.reconfigure(state.framesPerBuffer, + state.layout, + Time(state.rate)); + + const size_t formatSize = formatSizeInBytes(m_parameters.format); + m_outBuffer.resize(state.framesPerBuffer * channelsCount * formatSize); + + // + // Allocate and initialize the "status" object for this pcm stream + // + + snd_pcm_status_t* status; + snd_pcm_status_alloca(&status); + if (!m_pcm) return; + snd_pcm_status(m_pcm, status); + + if (!m_pcm) return; + snd_pcm_state_t pcmState = snd_pcm_state(m_pcm); + + if (pcmState == SND_PCM_STATE_SETUP) + { + if (!m_pcm) return; + if ((err = snd_pcm_prepare(m_pcm)) < 0) + { + cerr << "ERROR: ASLA: " << snd_strerror(err) << endl; + return; + } + + if (!m_pcm) return; + pcmState = snd_pcm_state(m_pcm); + } + + m_startSample = 0; + + for (size_t count = 0; + pcmState != SND_PCM_STATE_DISCONNECTED && + pcmState != SND_PCM_STATE_XRUN && + (isPlaying() || + (m_startSample % m_periodSize != 0) || + m_parameters.holdOpen); + count++) + { + // + // Figure out the current latency by hook or by crook. + // + + if (!m_pcm) return; + snd_pcm_status(m_pcm, status); + snd_pcm_sframes_t latencyFrames = snd_pcm_status_get_delay(status); + + if ((!latencyFrames || state.latency == 0) && count > 0) + { + // + // Update the latency using snd_pcm_delay because + // snd_pcm_status_get_delay() failed. This is common with + // the default device and/or dmix. + // + + if (!m_pcm) return; + if (!snd_pcm_delay(m_pcm, &latencyFrames) && latencyFrames > 0) + { + // + // Only if we think this actually worked + // (i.e. latencyFrames > 0) + // + + m_deviceState.latency = m_parameters.latency + latencyFrames / double(state.rate); + } + else + { + m_deviceState.latency = m_parameters.latency; + } + } + + // + // Call the evaluation function + // + + buffer.reconfigure(state.framesPerBuffer, + state.layout, + Time(state.rate), + samplesToTime(m_startSample, state.rate)); + + buffer.zero(); + + // + // Fetch the samples + // + + audioFillBuffer(buffer); + + // + // Convert to the correct output format + // + + unsigned char* out = &m_outBuffer.front(); + + switch (m_parameters.format) + { + case Float32Format: + memcpy(out, buffer.pointer(), buffer.sizeInBytes()); + break; + + case Int16Format: + transform(buffer.pointer(), + buffer.pointer() + buffer.sizeInFloats(), + (short*)out, + toType); + break; + + case Int32Format: + transform(buffer.pointer(), + buffer.pointer() + buffer.sizeInFloats(), + (signed int*)out, + toType); + break; + + case Int8Format: + transform(buffer.pointer(), + buffer.pointer() + buffer.sizeInFloats(), + (signed char*)out, + toType); + break; + } + + // + // Write it. This may block waiting for the device. I'm not + // sure why we have to account for chopping up the output: + // sometimes ALSA just doesn't write all the samples and you + // have to call write again. + // + + size_t total = 0; + snd_pcm_sframes_t frames = 0; + + if (!m_pcm) return; + pcmState = snd_pcm_state(m_pcm);; + + // + // Write the samples. + // + // NOTE: snd_pcm_writei() can BLOCK for an indefinite amount + // of time. + // + + for (size_t wcount = 0; + (pcmState == SND_PCM_STATE_RUNNING || + pcmState == SND_PCM_STATE_PREPARED) && + total < buffer.size()&& + frames >= 0 && + wcount < buffer.size(); + wcount++) + { + if (!m_pcm) return; + if (debug) + { + cerr << "INFO: pcm_writei: wcount " << wcount << + ", count " << count << + ", total " << total << + ", chan " << channelsCount << + ", formatSize " << formatSize << + ", bufferSize " << buffer.size() << endl; + } + frames = snd_pcm_writei(m_pcm, + out + (total * channelsCount * formatSize), + buffer.size() - total); + if (debug) cerr << "INFO: pcm_writei complete, frames " << frames << endl; + + if (frames >= 0) + { + total += frames; + } + else + { + // + // Possible underrun (in alsa parlance) + // + + // too new to use + + if (debug) + { + cerr << "ERROR: ASLA: write failed (" + << frames + << "): " << snd_strerror(frames) + << " -- "; + + switch (frames) + { + case -EBADFD: cerr << "bad pcm state"; break; + case -EPIPE: cerr << "underrun occured"; break; + case -ESTRPIPE: cerr << "suspend event occured"; break; + } + + cerr << endl; + } + + if (!m_pcm) return; +#ifdef USE_SAFE_ALSA + frames = snd_pcm_recover(m_pcm, frames, 0); +#else + frames = snd_pcm_prepare(m_pcm); +#endif + break; + } + if (total < buffer.size()) + { + if (!m_pcm) return; + pcmState = snd_pcm_state(m_pcm); + } + } + + m_startSample += total; + if (!m_pcm) return; + pcmState = snd_pcm_state(m_pcm); + + if (pcmState == SND_PCM_STATE_XRUN) + { + if (!m_pcm) return; + if (snd_pcm_prepare(m_pcm) < 0) break; + if (!m_pcm) return; + if ((pcmState = snd_pcm_state(m_pcm)) == SND_PCM_STATE_XRUN) break; + } + } + + if (!m_pcm) return; + if (pcmState == SND_PCM_STATE_RUNNING) snd_pcm_drop(m_pcm); + + m_threadGroup.lock(m_runningLock); + m_audioThreadRunning = false; + m_threadGroup.unlock(m_runningLock); + + if (debug) cout << "DEBUG: audio thread exiting" << endl; +} + +void +ALSAAudioRenderer::play(Session* s) +{ + AudioRenderer::play(s); + + s->audioVarLock(); + s->setAudioTimeShift(numeric_limits::max()); + s->setAudioStartSample(0); + s->setAudioFirstPass(true); + s->audioVarUnLock(); + + s->audioConfigure(); + + if (!isPlaying()) play(); +} + +void +ALSAAudioRenderer::play() +{ + AudioRenderer::play(); + + bool notconfigured = m_pcm == 0; + configureDevice(); + + // + // Dispatch the playback thread if its not running already. The + // last argument (async=false) indicates that we want to wait + // until the thread is running before the function returns. + // + + if (notconfigured) m_threadGroup.dispatch(trampoline, this); +} + +void +ALSAAudioRenderer::stop(Session* s) +{ + AudioRenderer::stop(s); + s->setAudioTimeShift(numeric_limits::max()); + if (!m_parameters.holdOpen) shutdown(); +} + +void +ALSAAudioRenderer::shutdown() +{ + // + // Shut down the hardware + // + + const bool holdOpen = m_parameters.holdOpen; + m_parameters.holdOpen = false; + + AudioRenderer::stop(); + + m_threadGroup.lock(m_runningLock); + bool isrunning = m_audioThreadRunning; + m_threadGroup.unlock(m_runningLock); + + if (isrunning) m_threadGroup.control_wait(true, 1.0); + + if (m_pcm) + { + // + // Although we tried to stop the audio threads above, we + // may have failed (IE the control_wait may have timed out, + // so lock-bracket the m_pcm=0 statement, and don't close + // the audio device until after m_pcm has been zeroed. + // + snd_pcm_t *tmp = m_pcm; + m_threadGroup.lock(m_runningLock); + m_pcm = 0; + m_threadGroup.unlock(m_runningLock); + snd_pcm_close(tmp); + } + + m_parameters.holdOpen = holdOpen; +} + + +} // Rv diff --git a/src/lib/audio/ALSAAudioModule/CMakeLists.txt b/src/lib/audio/ALSAAudioModule/CMakeLists.txt new file mode 100644 index 000000000..2ecc5bdad --- /dev/null +++ b/src/lib/audio/ALSAAudioModule/CMakeLists.txt @@ -0,0 +1,36 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "ALSAAudioModule" +) + +SET(_sources + ALSAAudioRenderer.cpp init.cpp +) + +ADD_LIBRARY( + ${_target} SHARED + ${_sources} +) + +# Note that the audio plugins are opened by the app with dlopen. We need the includes to compile from some libs to compile, but we don't want to link with them +# to avoid duplicating symbols when the dlopen occurs +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} "$" +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE TwkAudio asound +) + +ADD_LINK_OPTIONS("-Wl,-E") + +RV_STAGE(TYPE "SHARED_LIBRARY" TARGET ${_target}) diff --git a/src/lib/audio/ALSAAudioModule/init.cpp b/src/lib/audio/ALSAAudioModule/init.cpp new file mode 100644 index 000000000..03cc8e512 --- /dev/null +++ b/src/lib/audio/ALSAAudioModule/init.cpp @@ -0,0 +1,16 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include + +extern "C" { + +IPCore::AudioRenderer* +CreateAudioModule(const IPCore::AudioRenderer::RendererParameters& p) +{ + return new IPCore::ALSAAudioRenderer(p); +} + +} diff --git a/src/lib/audio/ALSASafeAudioModule/ALSASafeAudioModule/ALSASafeAudioRenderer.h b/src/lib/audio/ALSASafeAudioModule/ALSASafeAudioModule/ALSASafeAudioRenderer.h new file mode 100644 index 000000000..2db37e777 --- /dev/null +++ b/src/lib/audio/ALSASafeAudioModule/ALSASafeAudioModule/ALSASafeAudioRenderer.h @@ -0,0 +1,101 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifndef __audio__ALSASafeAudioRenderer__h__ +#define __audio__ALSASafeAudioRenderer__h__ +#include +#include +#include +#include + +#include + +namespace IPCore { + +/// Pure ALSASafe implementation of AudioRenderer + +/// +/// +/// + +class ALSASafeAudioRenderer : public AudioRenderer +{ +public: + // + // Types + // + + struct ALSADevice + { + std::string plug; + std::string card; + std::string name; + }; + + typedef std::vector ALSADeviceVector; + typedef TwkAudio::AudioBuffer AudioBuffer; + typedef stl_ext::thread_group ThreadGroup; + typedef std::vector OutputSampleBuffer; + typedef TwkUtil::Timer Timer; + + ALSASafeAudioRenderer(const RendererParameters&); + ~ALSASafeAudioRenderer(); + + + /// + /// play() will return almost immediately -- a worker thread will + /// be released and start playing. + /// + + virtual void play(); + virtual void play(Session*); + + /// + /// stop() will cause the worker thread to wait until play. A + /// subsequent play should restart it. + /// + + virtual void stop(Session*); + + /// + /// Completely stop the audio hardware and the audio thread. You can't + /// call play() after you've called shutdown() + /// + + virtual void shutdown(); + + /// + /// These are currently ignored by ALSAAudioRenderer + /// + + virtual void availableLayouts(const Device&, LayoutsVector&); + virtual void availableFormats(const Device&, FormatVector&); + virtual void availableRates(const Device&, Format, RateVector&); + + void threadMain(); + void configureDevice(); + +protected: + virtual void createDeviceList(); + +private: + OutputSampleBuffer m_outBuffer; + ALSADeviceVector m_alsaDevices; + ThreadGroup m_threadGroup; + size_t m_startSample; + snd_pcm_t* m_pcm; + snd_pcm_uframes_t m_periodSize; + snd_pcm_uframes_t m_bufferSize; + pthread_t m_audioThread; + bool m_audioThreadRunning; + pthread_mutex_t m_runningLock; +}; + + +} // IPCore + +#endif // __audio__ALSASafeAudioRenderer__h__ diff --git a/src/lib/audio/ALSASafeAudioModule/ALSASafeAudioRenderer.cpp b/src/lib/audio/ALSASafeAudioModule/ALSASafeAudioRenderer.cpp new file mode 100644 index 000000000..5495d7c02 --- /dev/null +++ b/src/lib/audio/ALSASafeAudioModule/ALSASafeAudioRenderer.cpp @@ -0,0 +1,1241 @@ +//****************************************************************************** +// Copyright (c) 2005 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// +// "Safe" ALSA is some unknown subset of the ALSA API that works +// under more conditions than the "not safe". This API is refered to +// by a PulseAudio developer here: +// +// http://0pointer.de/blog/projects/guide-to-sound-apis.html +// +// Here's the important excerpt: +// +// You want to know more about the safe ALSA subset? +// +// Here's a list of DOS and DONTS in the ALSA API if you care about +// that you application stays future-proof and works fine with +// non-hardware backends or backends for user-space sound drivers +// such as Bluetooth and FireWire audio. Some of these +// recommendations apply for people using the full ALSA API as +// well, since some functionality should be considered obsolete for +// all cases. +// +// If your application's code does not follow these rules, you must +// have a very good reason for that. Otherwise your code should +// simply be considered broken! +// +// DONTS: +// +// * Do not use "async handlers", e.g. via +// snd_async_add_pcm_handler() and friends. Asynchronous +// handlers are implemented using POSIX signals, which is a +// very questionable use of them, especially from libraries +// and plugins. Even when you don't want to limit yourself to +// the safe ALSA subset it is highly recommended not to use +// this functionality. Read this for a longer explanation why +// signals for audio IO are evil. +// +// * Do not parse the ALSA configuration file yourself or with +// any of the ALSA functions such as snd_config_xxx(). If you +// need to enumerate audio devices use snd_device_name_hint() +// (and related functions). That is the only API that also +// supports enumerating non-hardware audio devices and audio +// devices with drivers implemented in userspace. +// +// * Do not parse any of the files from /proc/asound/. Those +// files only include information about kernel sound drivers +// -- user-space plugins are not listed there. Also, the set +// of kernel devices might differ from the way they are +// presented in user-space. (i.e. sub-devices are mapped in +// different ways to actual user-space devices such as +// surround51 an suchlike. +// +// * Do not rely on stable device indexes from ALSA. Nowadays +// they depend on the initialization order of the drivers +// during boot-up time and are thus not stable. +// +// * Do not use the snd_card_xxx() APIs. For enumerating use +// snd_device_name_hint() (and related +// functions). snd_card_xxx() is obsolete. It will only list +// kernel hardware devices. User-space devices such as sound +// servers, Bluetooth audio are not included. snd_card_load() +// is completely obsolete in these days. +// +// * Do not hard-code device strings, especially not hw:0 or +// plughw:0 or even dmix -- these devices define no channel +// mapping and are mapped to raw kernel devices. It is highly +// recommended to use exclusively default as device +// string. If specific channel mappings are required the +// correct device strings should be front for stereo, +// surround40 for Surround 4.0, surround41, surround51, and +// so on. Unfortunately at this point ALSA does not define +// standard device names with channel mappings for non-kernel +// devices. This means default may only be used safely for +// mono and stereo streams. You should probably prefix your +// device string with plug: to make sure ALSA transparently +// reformats/remaps/resamples your PCM stream for you if the +// hardware/backend does not support your sampling parameters +// natively. +// +// * Do not assume that any particular sample type is supported +// except the following ones: U8, S16_LE, S16_BE, S32_LE, +// S32_BE, FLOAT_LE, FLOAT_BE, MU_LAW, A_LAW. +// +// * Do not use snd_pcm_avail_update() for synchronization +// purposes. It should be used exclusively to query the +// amount of bytes that may be written/read right now. Do not +// use snd_pcm_delay() to query the fill level of your +// playback buffer. It should be used exclusively for +// synchronisation purposes. Make sure you fully understand +// the difference, and note that the two functions return +// values that are not necessarily directly connected! +// +// * Do not assume that the mixer controls always know dB +// information. +// +// * Do not assume that all devices support MMAP style buffer +// access. +// +// * Do not assume that the hardware pointer inside the +// (possibly mmaped) playback buffer is the actual position +// of the sample in the DAC. There might be an extra latency +// involved. +// +// * Do not try to recover with your own code from ALSA error +// conditions such as buffer under-runs. Use +// snd_pcm_recover() instead. +// +// * Do not touch buffering/period metrics unless you have +// specific latency needs. Develop defensively, handling +// correctly the case when the backend cannot fulfill your +// buffering metrics requests. Be aware that the buffering +// metrics of the playback buffer only indirectly influence +// the overall latency in many cases. i.e. setting the buffer +// size to a fixed value might actually result in practical +// latencies that are much higher. +// +// * Do not assume that snd_pcm_rewind() is available and works +// and to which degree. +// +// * Do not assume that the time when a PCM stream can receive +// new data is strictly dependant on the sampling and +// buffering parameters and the resulting average +// throughput. Always make sure to supply new audio data to +// the device when it asks for it by signalling "writability" +// on the fd. (And similarly for capturing) +// +// * Do not use the "simple" interface snd_spcm_xxx(). +// +// * Do not use any of the functions marked as "obsolete". +// +// * Do not use the timer, midi, rawmidi, hwdep subsystems. +// +// DOS: +// +// * Use snd_device_name_hint() for enumerating audio devices. +// +// * Use snd_smixer_xx() instead of raw snd_ctl_xxx() +// +// * For synchronization purposes use snd_pcm_delay(). +// +// * For checking buffer playback/capture fill level use +// snd_pcm_update_avail(). +// +// * Use snd_pcm_recover() to recover from errors returned by any +// of the ALSA functions. +// +// * If possible use the largest buffer sizes the device +// supports to maximize power saving and drop-out safety. Use +// snd_pcm_rewind() if you need to react to user input +// quickly. +// + +#define USE_SAFE_ALSA 1 + +#include + +namespace IPCore { +using namespace std; +using namespace stl_ext; +using namespace TwkApp; +using namespace TwkAudio; + +namespace { + +template T toType (float a) +{ + return T(double(a) * double(numeric_limits::max())); +} + +} + +static void +alsaErrorHandler (const char *file, + int line, + const char *function, + int err, + const char *fmt, + ...) +{ + // don't print out ALSA asserts +} + +static int +snd_pcm_open_real_hard(snd_pcm_t **pcm, + const char *name, + snd_pcm_stream_t stream, + int mode, + size_t iterations = 1) +{ + // + // Try REAL HARD to open the device. If it doesn't open right + // away. Wait a second and see if anything improves. There's + // probably a way to avoid this by waiting until the device is + // drained, but one might assume snd_pcm_drain() would do that + // wouldn't one? + // + // It seems like the device is still playing back accumulated + // samples and cannot be opened (e.g., switching to an exclusive + // hardware device from default). If you wait for the device to + // drain, it will eventually work. This technique (or a + // variation) is applied by a number of other programs on the + // net. + // + + int err = snd_pcm_open(pcm, name, stream, mode); + +#if 0 + for (size_t i = 0; i < iterations && err == -EBUSY; i++) + { + usleep(10000); + err = snd_pcm_open(pcm, name, stream, mode); + } +#endif + + if (!err && *pcm) snd_pcm_nonblock(*pcm, 0); // 0 means block + return err; +} + +ALSASafeAudioRenderer::ALSASafeAudioRenderer(const RendererParameters& p) + : AudioRenderer(p), + m_threadGroup(1,2), + m_pcm(0), + m_audioThread(pthread_self()), + m_audioThreadRunning(false) +{ + snd_lib_error_set_handler(alsaErrorHandler); + pthread_mutex_init(&m_runningLock, 0); + + if (m_parameters.rate == 0) m_parameters.rate = TWEAK_AUDIO_DEFAULT_SAMPLE_RATE; + if (m_parameters.framesPerBuffer == 0) m_parameters.framesPerBuffer = 512; + if (m_parameters.device == "") m_parameters.device = "default"; + + createDeviceList(); + + if (debug) outputParameters(m_parameters); + + #ifndef PLATFORM_WINDOWS + /* + We used to initialize the device at this point, but if something + goes wrong, it means we can't even change the device in the + prefs, because audio is shutdown. If we skip this step, we'll + hold open the device after the first play(). + */ + + if (m_parameters.holdOpen) + { + play(); + AudioRenderer::stop(); + } + #endif +} + +ALSASafeAudioRenderer::~ALSASafeAudioRenderer() +{ + AudioRenderer::stop(); + shutdown(); + pthread_mutex_destroy(&m_runningLock); +} + +void +ALSASafeAudioRenderer::createDeviceList() +{ + m_outputDevices.clear(); + m_alsaDevices.clear(); + +#ifdef USE_SAFE_ALSA + // + // use the snd_device_name_hint() function + // + + void** deviceList = 0; + snd_device_name_hint(-1, "pcm", (void***)&deviceList); + + // + // For whatever reason ("pulseaudio sucks" ?) the list of devices returned + // here is sometimes incomplete (missing the "pusle" device). But calling + // the same call again seems to work around the problem ... + // + + snd_device_name_free_hint(deviceList); + snd_device_name_hint(-1, "pcm", (void***)&deviceList); + + for (void** dv = deviceList; *dv; dv++) + { + char* namep = snd_device_name_get_hint(*dv, "NAME"); + char* descp = snd_device_name_get_hint(*dv, "DESC"); + const string name = namep; + const string desc = descp; + + // + // we only do stereo right now + // + if (name == "null" || name.find("surround") != string::npos) continue; + + string::size_type n = desc.find('\n'); + const string desc0 = desc.substr(0, n); + const string desc1 = desc.substr(n+1, desc.size()); + + free(namep); + free(descp); + + Device d(name); + d.layout = TwkAudio::UnknownLayout; + d.defaultRate = 0; + d.isDefaultDevice = false; + d.index = m_outputDevices.size(); + + ALSADevice ad; + ad.plug = "plugIn"; + ad.name = name; + ad.card = ""; + + m_outputDevices.push_back(d); + m_alsaDevices.push_back(ad); + } + + snd_device_name_free_hint(deviceList); + +#else + + for (int card = -1; snd_card_next(&card) >= 0 && card >= 0;) + { + // + // Find the card devices + // + + snd_ctl_t* ctl = 0; + char* cardname = 0; + ostringstream hwcard; + hwcard << "hw:" << card; + + snd_card_get_longname(card, &cardname); + + if (snd_ctl_open(&ctl, hwcard.str().c_str(), 0) == 0) + { + snd_pcm_info_t* info = 0; + snd_pcm_info_alloca(&info); + + for (int device = -1; snd_ctl_pcm_next_device(ctl, &device) >= 0 && device >= 0;) + { + snd_pcm_info_set_device(info, device); + snd_pcm_info_set_subdevice(info, 0); + snd_pcm_info_set_stream(info, SND_PCM_STREAM_PLAYBACK); + + if (snd_ctl_pcm_info(ctl, info) >= 0) + { + ostringstream fullname; + + fullname << cardname + << ": " << snd_pcm_info_get_name(info) + << " (" << hwcard.str() + << "," << device + << ")"; + + ostringstream dname; + dname << hwcard.str() << "," << device; + + Device d(fullname.str()); + d.layout = TwkAudio::UnknownLayout; + d.defaultRate = 0; + d.isDefaultDevice = false; + d.index = m_outputDevices.size(); + + char* c; + snd_card_get_name(card, &c); + + ALSADevice ad; + ad.plug = "hw"; + ad.name = dname.str(); + ad.card = c; + + m_outputDevices.push_back(d); + m_alsaDevices.push_back(ad); + } + } + + snd_ctl_close(ctl); + } + } + +#endif + + // + // See if RV_ALSA_EXTRA_DEVICES is set and if so scarf the + // devices out of there too. + // + + if (const char* xdevices = getenv("RV_ALSA_EXTRA_DEVICES")) + { + vector tokens; + stl_ext::tokenize(tokens, xdevices, "|"); + + for (size_t i = 0; i < tokens.size(); i++) + { + vector dtokens; + stl_ext::tokenize(dtokens, tokens[i], "@"); + + string name; + string alsaDevice; + + if (dtokens.size() == 1) + { + name = tokens[i]; + alsaDevice = name; + } + else if (dtokens.size() == 2) + { + name = dtokens[0]; + alsaDevice = dtokens[1]; + } + else + { + cerr << "ERROR: RV_ALSA_EXTRA_DEVICES env variable -- syntax error" << endl; + break; + } + + Device d(name); + d.layout = TwkAudio::Stereo_2; + d.defaultRate = TWEAK_AUDIO_DEFAULT_SAMPLE_RATE; + d.latencyLow = 0; + d.latencyHigh = 0; + d.isDefaultDevice = false; + d.index = m_outputDevices.size(); + + ALSADevice ad; + ad.plug = "plughw"; + ad.card = "PlugIn Module"; + ad.name = alsaDevice; + + m_outputDevices.push_back(d); + m_alsaDevices.push_back(ad); + } + } + + // + // Add default + // + + Device d("default"); + d.layout = TwkAudio::Stereo_2; + d.defaultRate = TWEAK_AUDIO_DEFAULT_SAMPLE_RATE; + d.latencyLow = 0; + d.latencyHigh = 0; + d.isDefaultDevice = true; + d.index = m_outputDevices.size(); + + ALSADevice ad; + ad.plug = "plughw"; + ad.card = "PlugIn Module"; + ad.name = "default"; + + m_outputDevices.push_back(d); + m_alsaDevices.push_back(ad); + + // + // Copy the current state from the parameters + // + + DeviceState state; + state.device = m_parameters.device; + state.format = m_parameters.format; + state.rate = m_parameters.rate; + state.latency = m_parameters.latency; + state.layout = m_parameters.layout; + state.framesPerBuffer = m_parameters.framesPerBuffer; + setDeviceState(state); + + // + // Force allocation of memory now + // + + int channelsCount = TwkAudio::channelsCount(state.layout); + m_abuffer.reconfigure(state.framesPerBuffer, state.layout, Time(state.rate), 0, 0); + m_outBuffer.resize(m_abuffer.size() * channelsCount * formatSizeInBytes(state.format)); +} + + +void +ALSASafeAudioRenderer::availableLayouts(const Device& d, LayoutsVector& layouts) +{ + layouts.clear(); + + layouts.push_back(TwkAudio::Mono_1); + layouts.push_back(TwkAudio::Stereo_2); +} + +void +ALSASafeAudioRenderer::availableFormats(const Device& d, FormatVector& formats) +{ + // + // The base class ensures that this function is not called when a + // device is opened. + // + + formats.clear(); + static Format allformats[4] = { Float32Format, Int32Format, Int16Format, Int8Format }; + + const ALSADevice& ad = m_alsaDevices[d.index]; + const string& deviceName = ad.name; + + for (size_t i = 0; i < 3; i++) + { + Format format = allformats[i]; + + snd_pcm_t* pcm; + int err; + + if ((err = snd_pcm_open_real_hard(&pcm, + deviceName.c_str(), + SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK, + 10)) == 0) + { + snd_pcm_hw_params_t* params; + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(pcm, params); + + unsigned int rrate = (unsigned int)m_parameters.rate; + + snd_pcm_format_t alsaformat; + + switch (format) + { + case Float32Format: alsaformat = SND_PCM_FORMAT_FLOAT_LE; break; + case Int32Format: alsaformat = SND_PCM_FORMAT_S32_LE; break; + case Int16Format: alsaformat = SND_PCM_FORMAT_S16_LE; break; + case Int8Format: alsaformat = SND_PCM_FORMAT_S8; break; + } + + int dir = 0; + snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); + bool ok = snd_pcm_hw_params_set_format(pcm, params, alsaformat) == 0; + snd_pcm_hw_params_set_channels(pcm, params, TwkAudio::channelsCount(m_parameters.layout)); + snd_pcm_hw_params_set_rate_near(pcm, params, &rrate, &dir); + + if (ok && rrate == m_parameters.rate) + { + int periods = 2; + snd_pcm_uframes_t period_size = 8192; + snd_pcm_uframes_t buffer_size = 0; + + snd_pcm_hw_params_set_periods(pcm, params, periods, 0); + snd_pcm_hw_params_set_buffer_size(pcm, params, (period_size * periods) >> 2); + + if (snd_pcm_hw_params(pcm, params) >= 0) + { + formats.push_back(format); + } + } + + snd_pcm_close(pcm); + } + else if (err == -EBUSY) + { + // + // The device isn't available + // + + break; + } + } +} + +void +ALSASafeAudioRenderer::availableRates(const Device& device, Format format, RateVector& rates) +{ + // + // The base class ensures that this function is not called when a + // device is opened. + // + + rates.clear(); + + static double standardSampleRates[] = { + 8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0, + 44100.0, 48000.0, 88200.0, 96000.0, 192000.0, -1 /* negative terminated list */ + }; + + const ALSADevice& ad = m_alsaDevices[device.index]; + const string& deviceName = ad.name; + + for (size_t i = 0; standardSampleRates[i] > 0; i++) + { + double rate = standardSampleRates[i]; + snd_pcm_t* pcm; + int err; + + if ((err = snd_pcm_open_real_hard(&pcm, + deviceName.c_str(), + SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK, + 10)) == 0) + { + snd_pcm_hw_params_t* params; + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(pcm, params); + + unsigned int rrate = (unsigned int)rate; + + snd_pcm_format_t alsaformat; + + switch (m_parameters.format) + { + case Float32Format: alsaformat = SND_PCM_FORMAT_FLOAT_LE; break; + case Int32Format: alsaformat = SND_PCM_FORMAT_S32_LE; break; + case Int16Format: alsaformat = SND_PCM_FORMAT_S16_LE; break; + case Int8Format: alsaformat = SND_PCM_FORMAT_S8; break; + } + + // + // This doesn't work -- why does ALSA say it does? + // + + int dir = 0; + snd_pcm_hw_params_set_access(pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED); + snd_pcm_hw_params_set_format(pcm, params, alsaformat); + snd_pcm_hw_params_set_channels(pcm, params, TwkAudio::channelsCount(m_parameters.layout)); + snd_pcm_hw_params_set_rate_near(pcm, params, &rrate, &dir); + + if (rrate == rate) rates.push_back((unsigned int)rate); + +#if 0 + int periods = 2; + snd_pcm_uframes_t period_size = 8192; + snd_pcm_uframes_t buffer_size = 0; + + snd_pcm_hw_params_set_periods(pcm, params, periods, 0); + snd_pcm_hw_params_set_buffer_size(pcm, params, (period_size * periods) >> 2); + + if (snd_pcm_hw_params(pcm, params) >= 0 && rrate == rate) + { + rates.push_back((unsigned int)rate); + } +#endif + + snd_pcm_close(pcm); + } + } +} + +static void +trampoline(void* data) +{ + ALSASafeAudioRenderer* a = (ALSASafeAudioRenderer*)data; + a->threadMain(); +} + +void +ALSASafeAudioRenderer::configureDevice() +{ + int err; + + if (!m_pcm) + { + string alsaDeviceName = "default"; + int dindex = findDeviceByName(m_parameters.device); + + if (dindex >= 0 && dindex < m_alsaDevices.size()) + { + const ALSADevice& ad = m_alsaDevices[dindex]; + alsaDeviceName = ad.name; + } + + if ((err = snd_pcm_open_real_hard(&m_pcm, + alsaDeviceName.c_str(), + SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK)) < 0) + { + cerr << "WARNING: ALSA: Playback open error (" + << err << "): " << snd_strerror(err) << endl; + + + if (alsaDeviceName != "default" && err != -16) /* busy */ + { + cerr << "WARNING: ALSA: trying default instead of " + << alsaDeviceName + << endl; + + if ((err = snd_pcm_open_real_hard(&m_pcm, + "default", + SND_PCM_STREAM_PLAYBACK, + SND_PCM_NONBLOCK)) < 0) + { + cerr << "WARNING: ALSA: no luck with default either" << endl; + setErrorCondition("Unable to open an audio device"); + } + else + { + cerr << "WARNING: ALSA: using default device instead" << endl; + m_deviceState.device = "default"; + alsaDeviceName = "default"; + } + } + else + { + setErrorCondition("Unable to open an audio device"); + } + } + + snd_pcm_hw_params_t* params; + snd_pcm_hw_params_alloca(¶ms); + snd_pcm_hw_params_any(m_pcm, params); + + snd_pcm_format_t alsaformat; + size_t dataSize = 0; + + switch (m_parameters.format) + { + case Float32Format: + alsaformat = SND_PCM_FORMAT_FLOAT_LE; + dataSize = sizeof(float); + break; + default: + case Int16Format: + alsaformat = SND_PCM_FORMAT_S16_LE; + dataSize = sizeof(short); + break; + case Int32Format: + alsaformat = SND_PCM_FORMAT_S32_LE; + dataSize = sizeof(signed int); + break; + case Int8Format: + alsaformat = SND_PCM_FORMAT_S8; + dataSize = sizeof(char); + break; + } + + unsigned int rrate = (unsigned int)m_parameters.rate; + int dir = 0; + + if (snd_pcm_hw_params_set_access(m_pcm, params, SND_PCM_ACCESS_RW_INTERLEAVED) < 0) + { + cerr << "ERROR: ALSA: access (interleaved) failed - can't continue" << endl; + outputParameters(m_parameters); + snd_pcm_close(m_pcm); + m_pcm = 0; + setErrorCondition("unable to configure audio device"); + } + + if (snd_pcm_hw_params_set_format(m_pcm, params, alsaformat) < 0) + { + if (snd_pcm_hw_params_set_format(m_pcm, params, SND_PCM_FORMAT_S16_LE) >= 0) + { + if (debug) cerr << "WARNING: ALSA: format falling back to Int16" << endl; + m_parameters.format = Int16Format; + } + else + { + cerr << "ERROR: ALSA: format failed - can't continue" << endl; + outputParameters(m_parameters); + snd_pcm_close(m_pcm); + m_pcm = 0; + setErrorCondition("unable to configure audio device"); + } + } + + int channelsCount = TwkAudio::channelsCount(m_parameters.layout); + + if (snd_pcm_hw_params_set_channels(m_pcm, params, channelsCount) < 0) + { + cerr << "ERROR: ALSA: can't use " << channelsCount + << " channels -- can't continue" << endl; + outputParameters(m_parameters); + snd_pcm_close(m_pcm); + m_pcm = 0; + setErrorCondition("unable to configure audio device"); + } + + if (snd_pcm_hw_params_set_rate_near(m_pcm, params, &rrate, &dir) < 0) + { + cerr << "ERROR: ALSA: unable to set rate near " << rrate + << " -- can't continue" << endl; + outputParameters(m_parameters); + snd_pcm_close(m_pcm); + m_pcm = 0; + setErrorCondition("unable to configure audio device"); + } + + snd_pcm_hw_params_set_periods_integer(m_pcm, params); + + // + // Reasonably low latency + // + + unsigned int periods = 2; + m_periodSize = snd_pcm_uframes_t(double(512) / double(48000.0) * double(m_parameters.rate)); + m_bufferSize = m_periodSize * periods * channelsCount * dataSize; + dir = 0; + + snd_pcm_hw_params_set_periods(m_pcm, params, periods, 0); + snd_pcm_hw_params_set_period_size_near(m_pcm, params, &m_periodSize, &dir); + snd_pcm_hw_params_set_buffer_size_near(m_pcm, params, &m_bufferSize); + + if ((err = snd_pcm_hw_params(m_pcm, params)) < 0) + { + cerr << "ERROR: ALSA: params failed: " << snd_strerror(err) << endl; + snd_pcm_close(m_pcm); + m_pcm = 0; + setErrorCondition("unable to configure audio device"); + } + + snd_pcm_hw_params_get_buffer_size(params, &m_bufferSize); + snd_pcm_hw_params_get_period_size(params, &m_periodSize, &dir); + periods = m_bufferSize / m_periodSize; + + // LATENCY: + // + // latency = periodsize * periods / (rate * bytes_per_frame) + // + // is this even correct? Some sources claim there are still + // other latencies beyond this. I really don't want to add an + // extra latency UI. + // + + DeviceState nstate; + nstate.device = m_parameters.device; + nstate.format = m_parameters.format; + nstate.rate = rrate; + nstate.layout = m_parameters.layout; + nstate.latency = m_parameters.latency + m_periodSize * periods / (nstate.rate * sizeof(short) * channelsCount); + // THIS WILL BLOW IT UP IF ITS NOT 512 + //nstate.framesPerBuffer = 2048; + nstate.framesPerBuffer = m_parameters.framesPerBuffer; + setDeviceState(nstate); + + if (debug) + { + cout << "DEBUG: alsa buffer_size = " << m_bufferSize << endl + << "DEBUG: alsa period_size = " << m_periodSize << endl + << "DEBUG: alsa periods = " << periods << endl + << "DEBUG: alsa device latency = " << nstate.latency << endl; + } + + // + // Wait for the device to be ready + // + + snd_pcm_wait(m_pcm, -1); + } +} + + +void +ALSASafeAudioRenderer::threadMain() +{ + // + // Set the thread priority if asked to do so + // + + const Rv::Options& opts = Rv::Options::sharedOptions(); + + if (opts.schedulePolicy) + { + sched_param sp; + memset(&sp, 0, sizeof(sched_param)); + sp.sched_priority = opts.displayPriority; + + unsigned int policy = SCHED_OTHER; + + if (!strcmp(opts.schedulePolicy, "SCHED_RR")) policy = SCHED_RR; + else if (!strcmp(opts.schedulePolicy, "SCHED_FIFO")) policy = SCHED_FIFO; + + if (policy != SCHED_OTHER) + { + if (sched_setscheduler(0, policy, &sp)) + { + cout << "ERROR: can't set thread priority" << endl; + } + } + else + { + if (setpriority(PRIO_PROCESS, 0, opts.displayPriority)) + { + cout << "ERROR: can't set thread priority" << endl; + } + } + } + + int err; + + // + // NOTE: it's possible for this thread to continue on as a + // zombie (after the control thread tried to stop it). In that + // case m_pcm will be null, so check for that whenever we're + // going to use it. This should stop the "asset failed: pcm" + // crashes we get from alsa. + // + if (!m_pcm) return; + + m_threadGroup.lock(m_runningLock); + m_audioThreadRunning = true; + m_audioThread = pthread_self(); + m_threadGroup.unlock(m_runningLock); + + // + // Get the current state. (yeah, I know it was just set above) + // + + const DeviceState& state = deviceState(); + + // + // Setup accumulation buffer + // + + int channelsCount = TwkAudio::channelsCount(state.layout); + + AudioBuffer buffer(state.framesPerBuffer, + state.layout, + state.rate); + + m_abuffer.reconfigure(state.framesPerBuffer, + state.layout, + Time(state.rate)); + + const size_t formatSize = formatSizeInBytes(m_parameters.format); + m_outBuffer.resize(state.framesPerBuffer * channelsCount * formatSize); + + // + // Allocate and initialize the "status" object for this pcm stream + // + + snd_pcm_status_t* status; + snd_pcm_status_alloca(&status); + if (!m_pcm) return; + snd_pcm_status(m_pcm, status); + + if (!m_pcm) return; + snd_pcm_state_t pcmState = snd_pcm_state(m_pcm); + + if (pcmState == SND_PCM_STATE_SETUP) + { + if (!m_pcm) return; + if ((err = snd_pcm_prepare(m_pcm)) < 0) + { + cerr << "ERROR: ASLA: " << snd_strerror(err) << endl; + return; + } + + if (!m_pcm) return; + pcmState = snd_pcm_state(m_pcm); + } + + m_startSample = 0; + + for (size_t count = 0; + pcmState != SND_PCM_STATE_DISCONNECTED && + pcmState != SND_PCM_STATE_XRUN && + (isPlaying() || + (m_startSample % m_periodSize != 0) || + m_parameters.holdOpen); + count++) + { + // + // Figure out the current latency by hook or by crook. + // + + if (!m_pcm) return; + snd_pcm_status(m_pcm, status); + snd_pcm_sframes_t latencyFrames = snd_pcm_status_get_delay(status); + + if ((!latencyFrames || state.latency == 0) && count > 0) + { + // + // Update the latency using snd_pcm_delay because + // snd_pcm_status_get_delay() failed. This is common with + // the default device and/or dmix. + // + + if (!m_pcm) return; + if (!snd_pcm_delay(m_pcm, &latencyFrames) && latencyFrames > 0) + { + // + // Only if we think this actually worked + // (i.e. latencyFrames > 0) + // + + m_deviceState.latency = m_parameters.latency + latencyFrames / double(state.rate); + } + else + { + m_deviceState.latency = m_parameters.latency; + } + } + + // + // Call the evaluation function + // + + buffer.reconfigure(state.framesPerBuffer, + state.layout, + Time(state.rate), + samplesToTime(m_startSample, state.rate)); + + buffer.zero(); + + // + // Fetch the samples + // + + audioFillBuffer(buffer); + + // + // Convert to the correct output format + // + + unsigned char* out = &m_outBuffer.front(); + + switch (m_parameters.format) + { + case Float32Format: + memcpy(out, buffer.pointer(), buffer.sizeInBytes()); + break; + + case Int16Format: + transform(buffer.pointer(), + buffer.pointer() + buffer.sizeInFloats(), + (short*)out, + toType); + break; + + case Int32Format: + transform(buffer.pointer(), + buffer.pointer() + buffer.sizeInFloats(), + (signed int*)out, + toType); + break; + + case Int8Format: + transform(buffer.pointer(), + buffer.pointer() + buffer.sizeInFloats(), + (signed char*)out, + toType); + break; + } + + // + // Write it. This may block waiting for the device. I'm not + // sure why we have to account for chopping up the output: + // sometimes ALSA just doesn't write all the samples and you + // have to call write again. + // + + size_t total = 0; + snd_pcm_sframes_t frames = 0; + + if (!m_pcm) return; + pcmState = snd_pcm_state(m_pcm);; + + // + // Write the samples. + // + // NOTE: snd_pcm_writei() can BLOCK for an indefinite amount + // of time. + // + + for (size_t wcount = 0; + (pcmState == SND_PCM_STATE_RUNNING || + pcmState == SND_PCM_STATE_PREPARED) && + total < buffer.size()&& + frames >= 0 && + wcount < buffer.size(); + wcount++) + { + if (!m_pcm) return; + if (debug) + { + cerr << "INFO: pcm_writei: wcount " << wcount << + ", count " << count << + ", total " << total << + ", chan " << channelsCount << + ", formatSize " << formatSize << + ", bufferSize " << buffer.size() << endl; + } + frames = snd_pcm_writei(m_pcm, + out + (total * channelsCount * formatSize), + buffer.size() - total); + if (debug) cerr << "INFO: pcm_writei complete, frames " << frames << endl; + + if (frames >= 0) + { + total += frames; + } + else + { + // + // Possible underrun (in alsa parlance) + // + + // too new to use + + if (debug) + { + cerr << "ERROR: ASLA: write failed (" + << frames + << "): " << snd_strerror(frames) + << " -- "; + + switch (frames) + { + case -EBADFD: cerr << "bad pcm state"; break; + case -EPIPE: cerr << "underrun occured"; break; + case -ESTRPIPE: cerr << "suspend event occured"; break; + } + + cerr << endl; + } + + if (!m_pcm) return; +#ifdef USE_SAFE_ALSA + frames = snd_pcm_recover(m_pcm, frames, 0); +#else + frames = snd_pcm_prepare(m_pcm); +#endif + break; + } + if (total < buffer.size()) + { + if (!m_pcm) return; + pcmState = snd_pcm_state(m_pcm); + } + } + + m_startSample += total; + if (!m_pcm) return; + pcmState = snd_pcm_state(m_pcm); + + if (pcmState == SND_PCM_STATE_XRUN) + { + if (!m_pcm) return; + if (snd_pcm_prepare(m_pcm) < 0) break; + if (!m_pcm) return; + if ((pcmState = snd_pcm_state(m_pcm)) == SND_PCM_STATE_XRUN) break; + } + } + + if (!m_pcm) return; + if (pcmState == SND_PCM_STATE_RUNNING) snd_pcm_drop(m_pcm); + + m_threadGroup.lock(m_runningLock); + m_audioThreadRunning = false; + m_threadGroup.unlock(m_runningLock); + + if (debug) cout << "DEBUG: audio thread exiting" << endl; +} + +void +ALSASafeAudioRenderer::play(Session* s) +{ + AudioRenderer::play(s); + + s->audioVarLock(); + s->setAudioTimeShift(numeric_limits::max()); + s->setAudioStartSample(0); + s->setAudioFirstPass(true); + s->audioVarUnLock(); + + s->audioConfigure(); + + if (!isPlaying()) play(); +} + +void +ALSASafeAudioRenderer::play() +{ + AudioRenderer::play(); + + bool notconfigured = m_pcm == 0; + configureDevice(); + + // + // Dispatch the playback thread if its not running already. The + // last argument (async=false) indicates that we want to wait + // until the thread is running before the function returns. + // + + if (notconfigured) m_threadGroup.dispatch(trampoline, this); +} + +void +ALSASafeAudioRenderer::stop(Session* s) +{ + AudioRenderer::stop(s); + s->setAudioTimeShift(numeric_limits::max()); + if (!m_parameters.holdOpen) shutdown(); +} + +void +ALSASafeAudioRenderer::shutdown() +{ + // + // Shut down the hardware + // + + const bool holdOpen = m_parameters.holdOpen; + m_parameters.holdOpen = false; + + AudioRenderer::stop(); + + m_threadGroup.lock(m_runningLock); + bool isrunning = m_audioThreadRunning; + m_threadGroup.unlock(m_runningLock); + + if (isrunning) m_threadGroup.control_wait(true, 1.0); + + if (m_pcm) + { + // + // Although we tried to stop the audio threads above, we + // may have failed (IE the control_wait may have timed out, + // so lock-bracket the m_pcm=0 statement, and don't close + // the audio device until after m_pcm has been zeroed. + // + snd_pcm_t *tmp = m_pcm; + m_threadGroup.lock(m_runningLock); + m_pcm = 0; + m_threadGroup.unlock(m_runningLock); + snd_pcm_close(tmp); + } + + m_parameters.holdOpen = holdOpen; +} + + +} // Rv diff --git a/src/lib/audio/ALSASafeAudioModule/CMakeLists.txt b/src/lib/audio/ALSASafeAudioModule/CMakeLists.txt new file mode 100644 index 000000000..bb82b98da --- /dev/null +++ b/src/lib/audio/ALSASafeAudioModule/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "ALSASafeAudioModule" +) + +SET(_sources + ALSASafeAudioRenderer.cpp init.cpp +) + +ADD_LIBRARY( + ${_target} SHARED + ${_sources} +) + +# Note that the audio plugins are opened by the app with dlopen. We need the includes to compile from some libs to compile, but we don't want to link with them +# to avoid duplicating symbols when the dlopen occurs +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} "$" "$" + "$" +) + +TARGET_LINK_LIBRARIES( + ${_target} + PRIVATE TwkAudio asound +) + +ADD_LINK_OPTIONS("-Wl,-E") + +RV_STAGE(TYPE "SHARED_LIBRARY" TARGET ${_target}) diff --git a/src/lib/audio/ALSASafeAudioModule/init.cpp b/src/lib/audio/ALSASafeAudioModule/init.cpp new file mode 100644 index 000000000..0df8abd19 --- /dev/null +++ b/src/lib/audio/ALSASafeAudioModule/init.cpp @@ -0,0 +1,16 @@ +// +// Copyright (C) 2023 Autodesk, Inc. All Rights Reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +#include + +extern "C" { + +IPCore::AudioRenderer* +CreateAudioModule(const IPCore::AudioRenderer::RendererParameters& p) +{ + return new IPCore::ALSASafeAudioRenderer(p); +} + +} diff --git a/src/lib/audio/CMakeLists.txt b/src/lib/audio/CMakeLists.txt new file mode 100644 index 000000000..95bac1bb0 --- /dev/null +++ b/src/lib/audio/CMakeLists.txt @@ -0,0 +1,11 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# +ADD_SUBDIRECTORY(TwkAudio) +ADD_SUBDIRECTORY(QTAudioRenderer) +IF(RV_TARGET_LINUX) + ADD_SUBDIRECTORY(ALSASafeAudioModule) + ADD_SUBDIRECTORY(ALSAAudioModule) +ENDIF() diff --git a/src/lib/audio/QTAudioRenderer/CMakeLists.txt b/src/lib/audio/QTAudioRenderer/CMakeLists.txt new file mode 100644 index 000000000..8edc06c61 --- /dev/null +++ b/src/lib/audio/QTAudioRenderer/CMakeLists.txt @@ -0,0 +1,48 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(CMAKE_AUTOUIC + ON +) +SET(CMAKE_AUTOMOC + ON +) +SET(CMAKE_AUTORCC + ON +) + +SET(_target + "QTAudioRenderer" +) + +FILE(GLOB _sources QTAudioRenderer.cpp QTAudioRenderer/QTAudioRenderer.h) +ADD_LIBRARY( + ${_target} STATIC + ${_sources} +) + +FIND_PACKAGE( + Qt5 + COMPONENTS Core Multimedia + REQUIRED +) + +# Note that the audio plugins are opened by the app with dlopen. We need the includes to compile from some libs to compile, but we don't want to link with them +# to avoid duplicating symbols when the dlopen occurs +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} "$" +) + +TARGET_LINK_LIBRARIES( + ${_target} + PUBLIC TwkMath TwkUtil Qt5::Core Qt5::Multimedia + PRIVATE TwkAudio +) + +RV_STAGE(TYPE "LIBRARY" TARGET ${_target}) diff --git a/src/lib/audio/QTAudioRenderer/QTAudioRenderer.cpp b/src/lib/audio/QTAudioRenderer/QTAudioRenderer.cpp new file mode 100644 index 000000000..db734330a --- /dev/null +++ b/src/lib/audio/QTAudioRenderer/QTAudioRenderer.cpp @@ -0,0 +1,1704 @@ +/// +// Copyright (c) 2013,2014 Tweak Software. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include + +// +// This define is really a workaround to avoid +// a 'Pollable event error' in the linux kernel +// when a QtAudio device is start and stopped often +// (for the case hold open false; i.e. audio resources +// are released on stop). +// If we run the audio in the main thread, this error +// does not occur. So thats the workaround, otherwise +// rv will crash. +// Followup; it looks like this is not longer +// reproducible under centos7 or my centos6 vm +// (parallels 11). So commenting out RUN_IN_MAIN_THREAD_ON_NO_HOLDOPEN. +// +// +// +#ifdef PLATFORM_LINUX +//#define RUN_IN_MAIN_THREAD_ON_NO_HOLDOPEN +#endif + +namespace IPCore { +using namespace std; + + +#ifdef DEBUG_QTAUDIO +#define QTAUDIO_DEBUG(_f) \ + if (AudioRenderer::debugVerbose) TwkUtil::Log("AUDIO") << _f << " Qthread:" << QThread::currentThread(); +#else +#define QTAUDIO_DEBUG(_f) +#endif + +static ENVVAR_BOOL( evApplyWasapiFix, "RV_AUDIO_APPLY_WASAPI_FIX", true ); + +//---------------------------------------------------------------------- +// QTAudioThread +// +// This is the Qt thread class that lives +// with main/UI thread but holds the +// audio thread and has interThread "emit" methods to invoke methods +// in the audio thread which control +// the start/resume/stop/open behavior of +// the QAudioOutput and QIODevice running within it. +// +//---------------------------------------------------------------------- + +QTAudioThread::QTAudioThread(QTAudioRenderer &audioRenderer, + QObject* parent) : + QThread(parent) + , m_parent(parent) + , m_ioDevice(0) + , m_audioOutput(0) + , m_audioRenderer(audioRenderer) + , m_preRollSamples(0) + , m_processedSamples(0) + , m_startSample(0) + , m_preRollDisable(false) + , m_startOfInitialization(0) + , m_endOfInitialization(-1) + , m_lastDeviceLatency(0) + , m_patch9355Enabled(false) + +{ + QTAUDIO_DEBUG("QTAudioThread") + + // Cache some format related variables so we dont have to + // precompute them each time. + m_bytesPerSample = audioRenderer.m_format.channelCount() * audioRenderer.m_format.sampleSize() / 8; + + m_preRollMode = m_audioRenderer.m_parameters.preRoll; + + qRegisterMetaType("IPCore::Session*"); + + m_patch9355Enabled = (getenv("RV_AUDIO_AUTO_ADJUST") != NULL); +} + +QTAudioThread::~QTAudioThread() +{ + detachAudioOutputDevice(); + disconnect(m_parent); +} + + +void +QTAudioThread::startMe() +{ +#ifdef RUN_IN_MAIN_THREAD_ON_NO_HOLDOPEN + if (!m_audioRenderer.m_parameters.holdOpen) + { + if (!createAudioOutput()) return; + m_audioOutput->setAudioOutputBufferSize(); + return; + } +#endif + + start(); + unsigned long waitTime = 0; // in ms; + do { + wait(300); // in ms; + waitTime += 300; + QTAUDIO_DEBUG("QTAudioThread::startMe(): waiting for thread to start...") + } while (!isRunning() && waitTime < 1000); + QMetaObject::invokeMethod(m_audioOutput, "setAudioOutputBufferSize", Qt::QueuedConnection); +} + + +size_t +QTAudioThread::processedSamples() const +{ + return m_processedSamples; +} + +void +QTAudioThread::setProcessedSamples(size_t n) +{ + m_mutex.lock(); + m_processedSamples = n; + m_mutex.unlock(); +} + +size_t +QTAudioThread::startSample() const +{ + return m_startSample; +} + +void +QTAudioThread::setStartSample(size_t n) +{ + m_mutex.lock(); + m_startSample = n; + m_mutex.unlock(); +} + +void +QTAudioThread::setDeviceLatency(double t) +{ + m_mutex.lock(); + m_audioRenderer.m_deviceState.latency = t + m_audioRenderer.m_parameters.latency; + m_mutex.unlock(); +} + +bool +QTAudioThread::preRollDisable() const +{ + return m_preRollDisable; +} + +void +QTAudioThread::setPreRollDisable(bool disable) +{ + m_mutex.lock(); + m_preRollDisable = disable; + m_mutex.unlock(); +} + + +void +QTAudioThread::setPreRollDelay(TwkAudio::Time t) +{ + m_mutex.lock(); + m_audioRenderer.setPreRollDelay(t); + if (t == 0 ) + { + m_preRollDisable = false; + m_preRollSamples = 0; + } + m_mutex.unlock(); +} + +int +QTAudioThread::bytesPerSample() const +{ + return m_bytesPerSample; +} + +size_t +QTAudioThread::framesPerBuffer() const +{ + return m_audioRenderer.m_parameters.framesPerBuffer; +} + + +bool +QTAudioThread::holdOpen() const +{ + return m_audioRenderer.m_parameters.holdOpen; +} + + +void +QTAudioThread::emitStartAudio() +{ + QTAUDIO_DEBUG("QTAudioThread::emitStartAudio") + +#ifdef RUN_IN_MAIN_THREAD_ON_NO_HOLDOPEN + if (!m_audioRenderer.m_parameters.holdOpen) + { + m_audioOutput->startAudio(); + return; + } +#endif + + QMetaObject::invokeMethod(m_audioOutput, "startAudio", Qt::QueuedConnection); +} + +const AudioRenderer::DeviceState& +QTAudioThread::deviceState() const +{ + return m_audioRenderer.deviceState(); +} + +void +QTAudioThread::setDeviceState(AudioRenderer::DeviceState &state) +{ + m_mutex.lock(); + m_audioRenderer.setDeviceState(state); + m_mutex.unlock(); +} + +void +QTAudioThread::emitResetAudio() +{ + QTAUDIO_DEBUG("QTAudioThread::emitResetAudio") + +#ifdef RUN_IN_MAIN_THREAD_ON_NO_HOLDOPEN + if (!m_audioRenderer.m_parameters.holdOpen) + { + m_audioOutput->resetAudio(); + return; + } +#endif + + QMetaObject::invokeMethod(m_audioOutput, "resetAudio", Qt::QueuedConnection); +} + +void +QTAudioThread::emitStopAudio() +{ + QTAUDIO_DEBUG("QTAudioThread::emitStopAudio") + +#ifdef RUN_IN_MAIN_THREAD_ON_NO_HOLDOPEN + if (!m_audioRenderer.m_parameters.holdOpen) + { + m_audioOutput->stopAudio(); + return; + } +#endif + + QMetaObject::invokeMethod(m_audioOutput, "stopAudio", Qt::BlockingQueuedConnection); +} + + +void +QTAudioThread::emitSuspendAudio() +{ + QTAUDIO_DEBUG("QTAudioThread::emitSuspendAudio") + +#ifdef RUN_IN_MAIN_THREAD_ON_NO_HOLDOPEN + if (!m_audioRenderer.m_parameters.holdOpen) + { + m_audioOutput->suspendAudio(); + return; + } +#endif + + QMetaObject::invokeMethod(m_audioOutput, "suspendAudio", Qt::BlockingQueuedConnection); +} + + +void +QTAudioThread::emitSuspendAndResetAudio() +{ + QTAUDIO_DEBUG("QTAudioThread::emitSuspendAndResetAudio") + +#ifdef RUN_IN_MAIN_THREAD_ON_NO_HOLDOPEN + if (!m_audioRenderer.m_parameters.holdOpen) + { + m_audioOutput->suspendAndResetAudio(); + return; + } +#endif + + QMetaObject::invokeMethod(m_audioOutput, "suspendAndResetAudio", Qt::BlockingQueuedConnection); +} + + +void +QTAudioThread::emitResetDevice() +{ + QTAUDIO_DEBUG("QTAudioThread::emitResetDevice") + +#ifdef RUN_IN_MAIN_THREAD_ON_NO_HOLDOPEN + if (!m_audioRenderer.m_parameters.holdOpen) + { + m_ioDevice->resetDevice(); + return; + } +#endif + + QMetaObject::invokeMethod(m_ioDevice, "resetDevice", Qt::QueuedConnection); +} + +void +QTAudioThread::emitStopDevice() +{ + QTAUDIO_DEBUG("QTAudioThread::emitStopDevice") + +#ifdef RUN_IN_MAIN_THREAD_ON_NO_HOLDOPEN + if (!m_audioRenderer.m_parameters.holdOpen) + { + m_ioDevice->stopDevice(); + return; + } +#endif + + QMetaObject::invokeMethod(m_ioDevice, "stopDevice", Qt::BlockingQueuedConnection); +} + +void +QTAudioThread::emitPlay(IPCore::Session *s) +{ + QTAUDIO_DEBUG("QTAudioThread::emitPlay") + +#ifdef RUN_IN_MAIN_THREAD_ON_NO_HOLDOPEN + if (!m_audioRenderer.m_parameters.holdOpen) + { + m_audioOutput->play(s); + return; + } +#endif + + // This needs to be a blocking connection to ensure play is as close to + // starting for real. This improves the av sync lag especially om Windows. + // + QMetaObject::invokeMethod(m_audioOutput, "play", Qt::BlockingQueuedConnection, Q_ARG(IPCore::Session*, s)); +} + +void +QTAudioThread::run() +{ + QTAUDIO_DEBUG("QTAudioThread::run") + TwkUtil::setThreadName("QTAudioThread"); + + // IMPORTANT: createAudioOutput() call + // can and must only be called within run() + // so that the QTAudioOuput and QTAudioIODevice + // is created within run()'s execution thread. + if (!createAudioOutput()) return; + + exec(); +} + +bool +QTAudioThread::createAudioOutput() +{ + QMutexLocker lock(&m_mutex); + if (AudioRenderer::debug) TwkUtil::Log("AUDIO") << "createAudioOutput()"; + + // Create the QAudioOutput and QIO devices + + m_bytesPerSample = m_audioRenderer.m_format.channelCount() * m_audioRenderer.m_format.sampleSize() / 8; + + if (m_ioDevice = new QTAudioIODevice(*this)) + { + if (m_audioOutput = new QTAudioOutput(m_audioRenderer.m_device, + m_audioRenderer.m_format, + *m_ioDevice, + *this)) + { + m_ioDevice->start(); + return true; + } + else + { + m_audioRenderer.setErrorCondition("Unable to create QIODevice for QAudioOutput."); + return false; + } + } + else + { + m_audioRenderer.setErrorCondition("Unable to create QAudioOutput."); + return false; + } + + return true; +} + +void +QTAudioThread::detachAudioOutputDevice() +{ + if (AudioRenderer::debug) TwkUtil::Log("AUDIO") << "detachAudioOutputDevice"; + + if (m_ioDevice) + { + emitStopDevice(); + } + + if (m_audioOutput) + { + emitStopAudio(); + } + + quit(); + wait(); + + if (m_audioOutput) + { + delete m_audioOutput; + m_audioOutput = 0; + } + + if (m_ioDevice) + { + delete m_ioDevice; + m_ioDevice = 0; + } + +} + +// +// This is the callback called by QTAudioIODevice::readData() for +// pushing audio data to the audio device. +// +qint64 +QTAudioThread::qIODeviceCallback(char* data, qint64 maxLenInBytes) +{ + if (m_patch9355Enabled && isInInitialization()) + { + TwkUtil::SystemClock::Time dura = endOfInitialization(); + setDeviceLatency(dura); + m_lastDeviceLatency = dura; + if (AudioRenderer::debug) TwkUtil::Log("AUDIO") << "Initialization duration:" << dura << "s"; + } + + if (AudioRenderer::debugVerbose) + { + TwkUtil::Log("AUDIO") + << "qIODeviceCallback: asking maxLenInBytes=" << (int) maxLenInBytes + << " isPlayingAudio=" << (int) m_audioRenderer.isPlaying() + << " audiooutput state=" << (int) m_audioOutput->state(); + } + + const AudioRenderer::DeviceState &state = deviceState(); + + if (data && + (m_audioRenderer.isPlaying()) && + (maxLenInBytes >= m_bytesPerSample) && + ((m_audioOutput->state() == QAudio::ActiveState) || + (m_audioOutput->state() == QAudio::IdleState))) + { + const int bufferSize = m_audioOutput->bufferSize(); + const int periodSize = m_audioOutput->periodSize(); +#ifdef PLATFORM_DARWIN + const bool doPreRoll = false; +#else + const bool doPreRoll = ((m_preRollMode && !m_preRollDisable && maxLenInBytes >= bufferSize - periodSize)?true:false); +#endif + +#ifdef PLATFORM_DARWIN + // + // Figure out the latency. + // Only valid for OSX as processedUSecs() doesnt return + // anything useful on Linux/Windows. + qint64 processedUSecs = m_audioOutput->processedUSecs(); + TwkAudio::Time actualPlayedTime = TwkAudio::Time(processedUSecs / 1000000.0); + TwkAudio::Time targetPlayedTime = TwkAudio::samplesToTime(m_processedSamples, state.rate); + setDeviceLatency(targetPlayedTime - actualPlayedTime); +#endif + + // + // I am qualifying this on a per platform basis. + // + // On OSX, I found that QAudioOutput requires that you write + // periodSize() bytes at a time. Otherwise I found subtle crackles + // in the playback. Try a sine 48kHz test. + // + // On Linux, we want to write no more than bufferSize. + // Writing anything smaller can potential cause a corrupt audio + // buffer when running multiple RV through mixer like pulse. + // +#ifdef PLATFORM_DARWIN + if (periodSize && maxLenInBytes > periodSize) + { + maxLenInBytes = periodSize; + } +#else + if (bufferSize && maxLenInBytes > bufferSize) + { + maxLenInBytes = bufferSize; + } +#endif + + // + // If the request bytes i.e. maxLenInBytes is within + // a periodSize worths of the bufferSize we do a silent preroll + // fill of the audio device. + // This prevents almost any lag time on start of play for an + // oversized audiobufferfill request. + // NB: This does not apply to DARWIN where writes happen + // at max periodSize amounts. + if (doPreRoll && m_preRollSamples == 0) + { + memset(data, 0, maxLenInBytes); + + const size_t preRollSamplesWritten = (size_t) (maxLenInBytes / m_bytesPerSample); + m_preRollSamples += preRollSamplesWritten; + m_processedSamples += preRollSamplesWritten; + + if (AudioRenderer::debug) + { + TwkUtil::Log("AUDIO") + << "qIODeviceCallback: PreRollWrite: " + << " bufferSize=" << bufferSize + << " periodSize=" << periodSize + << " maxLenInBytes=" << maxLenInBytes + << " PreRollDelay=" << TwkAudio::samplesToTime(preRollSamplesWritten, state.rate); + } + + return maxLenInBytes; + } + + if (m_preRollMode && !m_preRollDisable && m_preRollSamples > 0) + { + // + // After much fiddling I found this to be the best rule. + // I noticed that there seems to be a residual buffer of one + // period size (noticed from the difference in the first two the buffer + // request when play first starts). + // NB: the preRoll delay is divided by the number of channels since + // this is actual delay in time during playback. + // +#ifdef PLATFORM_LINUX + TwkAudio::Time writtenPreRollTime = + TwkAudio::samplesToTime(m_preRollSamples-(periodSize /m_bytesPerSample), state.rate) / TwkAudio::channelsCount(state.layout); +#else + TwkAudio::Time writtenPreRollTime = + TwkAudio::samplesToTime(m_preRollSamples, state.rate) / TwkAudio::channelsCount(state.layout); +#endif + + m_audioRenderer.setPreRollDelay(writtenPreRollTime); + + if (AudioRenderer::debug) + { + TwkUtil::Log("AUDIO") + << "qIODeviceCallback: PreRollWrite: " + << " Total PreRoll Delay=" << m_audioRenderer.preRollDelay(); + } + } + + m_preRollDisable = true; + + if (AudioRenderer::debugVerbose) + { + TwkUtil::Log("AUDIO") + << "qIODeviceCallback: NormalWrite: " + << " m_startSample=" << m_startSample + << " startSampleTime=" << TwkAudio::samplesToTime(m_startSample, state.rate) + << " m_deviceState.latency: " << m_audioRenderer.m_deviceState.latency + << " maxLenInBytes=" << maxLenInBytes; + } + + size_t numSamplesToWrite = (size_t) (maxLenInBytes / m_bytesPerSample); + size_t numSamplesForAbuffer = numSamplesToWrite; + size_t bytesWrittenToDevice = 0; + + if (numSamplesForAbuffer) + { + QMutexLocker lock(&m_mutex); + + // Fetch the samples + // + m_abuffer.reconfigure(numSamplesForAbuffer, + state.layout, + TwkAudio::Time(state.rate), + TwkAudio::samplesToTime(m_startSample, state.rate)); + + m_abuffer.zero(); + + try + { + m_audioRenderer.audioFillBuffer(m_abuffer); + } + catch (std::exception &exc) + { + cout << "WARNING: QAudio fillBuffer exception: " << exc.what() << endl; + } + + switch (state.format) + { + case TwkAudio::Float32Format: + bytesWrittenToDevice = m_abuffer.sizeInBytes(); + m_startSample += numSamplesForAbuffer; + m_processedSamples += numSamplesForAbuffer; + memcpy(data, m_abuffer.pointer(), bytesWrittenToDevice); + break; + + case TwkAudio::Int32Format: + bytesWrittenToDevice = m_abuffer.sizeInBytes(); + m_startSample += numSamplesForAbuffer; + m_processedSamples += numSamplesForAbuffer; + transform(m_abuffer.pointer(), + m_abuffer.pointer() + m_abuffer.sizeInFloats(), + (int*) data, + AudioRenderer::toType); + break; + + case TwkAudio::Int24Format: + bytesWrittenToDevice = m_abuffer.sizeInFloats() * sizeof(char) * 3; + m_startSample += numSamplesForAbuffer; + m_processedSamples += numSamplesForAbuffer; + AudioRenderer::transformFloat32ToInt24(m_abuffer.pointer(), + data, + m_abuffer.sizeInFloats(), + (m_audioRenderer.m_format.byteOrder() == QAudioFormat::LittleEndian)); + break; + + case TwkAudio::Int16Format: + bytesWrittenToDevice = m_abuffer.sizeInFloats() * sizeof(short); + m_startSample += numSamplesForAbuffer; + m_processedSamples += numSamplesForAbuffer; + transform(m_abuffer.pointer(), + m_abuffer.pointer() + m_abuffer.sizeInFloats(), + (short*) data, + AudioRenderer::toType); + break; + + case TwkAudio::Int8Format: + bytesWrittenToDevice = m_abuffer.sizeInFloats() * sizeof(signed char); + m_startSample += numSamplesForAbuffer; + m_processedSamples += numSamplesForAbuffer; +#ifdef PLATFORM_DARWIN + transform(m_abuffer.pointer(), + m_abuffer.pointer() + m_abuffer.sizeInFloats(), + (signed char*) data, + AudioRenderer::toType); +#else + transform(m_abuffer.pointer(), + m_abuffer.pointer() + m_abuffer.sizeInFloats(), + (unsigned char*) data, + AudioRenderer::toUnsignedType); +#endif + break; + + default: + cout << "WARNING: Unsupported format for QAudio: " << endl; + break; + } + } + + if (AudioRenderer::debugVerbose) + { + TwkUtil::Log("AUDIO") + << "qIODeviceCallback: NormalWrite: " + << " numSamplesForAbuffer=" << numSamplesForAbuffer + << " bytesWrittenToDevice=" << bytesWrittenToDevice + << " m_processedSamples=" << m_processedSamples; + + if (bytesWrittenToDevice != maxLenInBytes) + { + cout << "AUDIO: qIODeviceCallback: WARNING bytesWrittenToDevice != maxLenInBytes" << endl; + } + } + + return ((qint64) bytesWrittenToDevice); + } + else + { + return 0; + } +} + +void +QTAudioThread::startOfInitialization() +{ + m_startOfInitialization = m_systemClock.now(); + m_endOfInitialization = -1; +} + +TwkUtil::SystemClock::Time +QTAudioThread::endOfInitialization() +{ + m_endOfInitialization = m_systemClock.now(); + return (m_endOfInitialization - m_startOfInitialization); +} + +bool QTAudioThread::isInInitialization() const +{ + return (m_endOfInitialization < 0); +} + +//---------------------------------------------------------------------- +// QTAudioIODevice +// +// This is the Qt audio IO device class that lives +// with the audio thread. +//---------------------------------------------------------------------- +QTAudioIODevice::QTAudioIODevice(QTAudioThread &audiothread) : + m_thread(audiothread) +{ + QTAUDIO_DEBUG("QTAudioIODevice:") +} + +QTAudioIODevice::~QTAudioIODevice() +{ +} + +void +QTAudioIODevice::start() +{ + QTAUDIO_DEBUG("QTAudioIODevice::start") + open(QIODevice::ReadOnly | QIODevice::Unbuffered); +} + +void +QTAudioIODevice::stopDevice() +{ + QTAUDIO_DEBUG("QTAudioIODevice::stopDevice") + close(); +} + +void +QTAudioIODevice::resetDevice() +{ + QTAUDIO_DEBUG("QTAudioIODevice::resetDevice") + reset(); +} + +qint64 QTAudioIODevice::readData(char* data, qint64 maxLenInBytes) +{ + // QTAUDIO_DEBUG("QTAudioIODevice::readData") + int bytesWritten = m_thread.qIODeviceCallback(data, maxLenInBytes); + return bytesWritten; +} + +qint64 QTAudioIODevice::writeData(const char* data, qint64 len) +{ + Q_UNUSED(data); + Q_UNUSED(len); + + return 0; +} + +qint64 QTAudioIODevice::bytesAvailable() const +{ + + return QIODevice::bytesAvailable(); +} + +//---------------------------------------------------------------------- +// QTAudioOutput +// +// This is the Qt audio output class that lives +// with the audio thread. +//---------------------------------------------------------------------- +QTAudioOutput::QTAudioOutput(QAudioDeviceInfo &audioDeviceInfo, + QAudioFormat &audioFormat, + QTAudioIODevice &ioDevice, + QTAudioThread &audioThread) : + QAudioOutput(audioDeviceInfo, audioFormat) + , m_device(audioDeviceInfo) + , m_format(audioFormat) + , m_ioDevice(ioDevice) + , m_thread(audioThread) +{ + QTAUDIO_DEBUG("QTAudioOutput") +} + +QTAudioOutput::~QTAudioOutput() +{ +} + +std::string QTAudioOutput::toString(QAudio::State state ) +{ + switch(state) + { + case QAudio::ActiveState: return "active"; + case QAudio::SuspendedState: return "suspended"; + case QAudio::StoppedState: return "stopped"; + case QAudio::IdleState: return "idle"; + default: return "???"; + } +} + +void +QTAudioOutput::startAudio() +{ + QTAUDIO_DEBUG("QTAudioOutput::startAudio check") + + if (state() == QAudio::StoppedState) + { + m_thread.startOfInitialization(); + + QTAUDIO_DEBUG("QTAudioOutput::startAudio start()") +#ifdef PLATFORM_WINDOWS + // This is needed for Windows because when QAudioOutput + // reset() or stop() is called (thus putting the + // device into the StoppedState), the bufferSize also gets + // reset to zero. + // So we need to recalc the buffer size we want to use + // again before calling start(). + setAudioOutputBufferSize(); +#endif + m_thread.setStartSample(0); + m_thread.setProcessedSamples(0); + m_thread.setPreRollDelay(0); + start(&m_ioDevice); + + } + else if (state() == QAudio::SuspendedState) + { + QTAUDIO_DEBUG("QTAudioOutput::startAudio resume()") +#ifdef PLATFORM_LINUX + m_thread.setPreRollDelay(0); +#endif + resume(); + } +} + +void +QTAudioOutput::resetAudio() +{ + QTAUDIO_DEBUG("QTAudioOutput::resetAudio") + reset(); +} + +void +QTAudioOutput::stopAudio() +{ + if (AudioRenderer::debug) TwkUtil::Log("AUDIO") << "Stop audio output"; + + QTAUDIO_DEBUG("QTAudioOutput::stopAudio") + + if (state() != QAudio::StoppedState) + { + stop(); + } +} + +void +QTAudioOutput::suspendAudio() +{ + QTAUDIO_DEBUG("QTAudioOutput::suspendAudio") + + if (state() != QAudio::StoppedState) + { + suspend(); + } +} + +void +QTAudioOutput::suspendAndResetAudio() +{ + QTAUDIO_DEBUG("QTAudioOutput::suspendAndResetAudio") + + if (state() != QAudio::StoppedState) + { + suspend(); + QTAUDIO_DEBUG("QTAudioOutput::suspendAndResetAudio - before reset") + reset(); + QTAUDIO_DEBUG("QTAudioOutput::suspendAndResetAudio - after reset") + } +} + +int +QTAudioOutput::calcAudioBufferSize(const int channels, + const int sampleRate, + const int sampleSizeInBytes, + const int defaultBufferSize) const +{ + // For sample rates 8k,11k 8bit, make the bufferSize + // small to avoid audio cache misses. + if (m_format.sampleRate() < 22000 && + m_thread.bytesPerSample() <= 2 ) + { + return 1024; + } + + // QAudioOutput's default is 8192 bytes. (OSX) + // QAudioOutput's default is 200ms for all channels. (Windows) + const float minBufferSizeInTimePerChannel = 0.1f; + + // Compute buffer size so it is at least minBufferSizeInTimePerChannel. + float defaultBufferSizeInTimePerChannel = (defaultBufferSize / sampleSizeInBytes) / ((float) (sampleRate * channels)); + + if (defaultBufferSizeInTimePerChannel < minBufferSizeInTimePerChannel) + { + return ((int) ceilf(minBufferSizeInTimePerChannel * ((float) (sampleRate * channels * sampleSizeInBytes)))); + } + else + { + return defaultBufferSize; + } +} + +void +QTAudioOutput::setAudioOutputBufferSize() +{ + QTAUDIO_DEBUG("QTAudioOutput::setAudioOutputBufferSize") + + // For linux we need to start the m_audioOutput to determine the buffersize thats + // used. + // On Windows, if we dont set the bufferSize (use the default), then the size of the buffer + // is set by the audioOutput start() call. + + // The AudioRenderer::defaultParameters().framesPerBuffer is zero it means the + // audio preference "Audio Cache samples" is zero; + // in which case we calculate what the audioOutput + // bufferSize should be. + if (AudioRenderer::defaultParameters().framesPerBuffer == 0) + { +#ifdef PLATFORM_DARWIN + setBufferSize(calcAudioBufferSize(m_format.channelCount(), + m_format.sampleRate(), + m_format.sampleSize() / 8, + bufferSize())); +#endif + +#ifdef PLATFORM_WINDOWS + // Note On Windows we need to set the buffer size 10x smaller because it is resize to + // a 10x value when the AudioOutput is started. + // First determine if this is a WASAPI device + // Note that on windows, an audio device can come from of those two Qt + // Audio Plugins: + // WASAPI (Windows Audio Session API) or WinMM (Windows Multimedia). + // Robert's fix above needs to be applied only to WASAPI audio devices. + // In Qt 5.12.5, there is no mechanism to identify from which audio + // plugin an audio device orginated. So for the time being we will always + // assume that it is an AWASPI device unless we detect that the WASAPI Qt + // audio plugin dll was removed from the Qt/Audio plugin directory. + // This is a temporary mechanism that will unblock our clients + // without introducing an RV preference that would have polluted the RV + // preferences unnecessarily. + const bool audioDeviceIsWASAPI = m_device.realm() == "wasapi"; + if (audioDeviceIsWASAPI && evApplyWasapiFix.getValue()) + { + setBufferSize(calcAudioBufferSize(m_format.channelCount(), + m_format.sampleRate(), + m_format.sampleSize() / 8, + bufferSize()) / 10); + } + else + { + setBufferSize(calcAudioBufferSize(m_format.channelCount(), + m_format.sampleRate(), + m_format.sampleSize() / 8, + bufferSize())); + } +#endif + } + else + { + // Note: This might not always succeed as audio device buffer cannot + // be set below a certain size. + // If you hear crackles on playback at high sample rates + // this is probably due to the buffer not being filled fast + // enough because it is too small... so a user can increase + // the size in the preference tab i.e. "Device Packet Size". + +#ifdef PLATFORM_LINUX + // For linux it is in ms * 1000. + int targetBufferSize = (1000000 * m_thread.framesPerBuffer() / m_format.sampleRate()); +#else + int targetBufferSize = m_thread.framesPerBuffer() * m_thread.bytesPerSample(); +#endif + setBufferSize(targetBufferSize); + if (bufferSize() != targetBufferSize) + { + cout << "Warning: Audio Device Cache of size " << m_thread.framesPerBuffer() << + " samples cannot be set below the device min sample limit of " + << bufferSize() / m_thread.bytesPerSample() + << " samples. Using device min limit instead." << endl; + } + } + + AudioRenderer::DeviceState newState = m_thread.deviceState(); + newState.framesPerBuffer = bufferSize() / m_thread.bytesPerSample(); + m_thread.setDeviceState(newState); + + if (AudioRenderer::debug) + { + TwkUtil::Log("AUDIO") << "setAudioOutputBufferSize: bufferSize= " << bufferSize(); + } +} + +void +QTAudioOutput::play(IPCore::Session* s) +{ + if (AudioRenderer::debug) TwkUtil::Log("QTAudioOutput") << "play()"; + + +#ifdef DEBUG_QTAUDIO + if (AudioRenderer::debugVerbose) + TwkUtil::Log("QTAudioThread") << "setup thread:" << QThread::currentThread() + << " isRunning=" << (int) m_thread.isRunning(); +#endif + + m_thread.setDeviceLatency(m_thread.getLastDeviceLatency()); + + AudioRenderer::DeviceState newState = m_thread.deviceState(); + newState.framesPerBuffer = bufferSize() / m_thread.bytesPerSample(); + m_thread.setDeviceState(newState); + + s->audioConfigure(); + + if (AudioRenderer::debug) + { + TwkUtil::Log("QTAudioOutput") << "play session:setup: startSample =" << m_thread.startSample() + << " shift=" << s->shift() + << " isPlaying=" << (int) s->isPlaying() + << " isScrubbingAudio=" << (int) s->isScrubbingAudio() + << " audioOutput state=" << (int) state() + << " audioOutput bufferSize=" << (int) bufferSize() + << " audioOutput periodSize=" << (int) periodSize(); + } +} + +//---------------------------------------------------------------------- +// QTAudioRenderer +// +// This is the audio render class that lives +// with the main/UI thread. +// +//---------------------------------------------------------------------- +QTAudioRenderer::QTAudioRenderer(const RendererParameters ¶ms, + QObject* parent) : + AudioRenderer(params) + , m_parent(parent) + , m_codec("audio/pcm") +{ + init(); + QTAUDIO_DEBUG("QTAudioRenderer") +} + +QTAudioRenderer::~QTAudioRenderer() +{ + if (m_thread) + { + delete m_thread; + } +} + +TwkAudio::Format +QTAudioRenderer::getTwkAudioFormat() const +{ + return convertToTwkAudioFormat(m_format.sampleSize(), + m_format.sampleType()); +} + +TwkAudio::Format +QTAudioRenderer::convertToTwkAudioFormat(int fmtSize, + QAudioFormat::SampleType fmtType) const +{ + switch (fmtSize) + { + case 8: + switch (fmtType) + { + case QAudioFormat::SignedInt: + return TwkAudio::Int8Format; + break; + + default: + break; + } + + break; + + case 16: + switch (fmtType) + { + case QAudioFormat::SignedInt: + return TwkAudio::Int16Format; + break; + + default: + break; + } + + break; + + case 24: + switch (fmtType) + { + case QAudioFormat::SignedInt: + return TwkAudio::Int24Format; + break; + + default: + break; + } + + break; + + case 32: + switch (fmtType) + { + case QAudioFormat::SignedInt: + return TwkAudio::Int32Format; + break; + + case QAudioFormat::Float: + return TwkAudio::Float32Format; + break; + + default: + break; + } + + break; + + case 64: + break; + } + + return TwkAudio::UnknownFormat; +} + +void +QTAudioRenderer::availableLayouts(const Device &d, LayoutsVector &layouts) +{ + const QAudioDeviceInfo &info = m_deviceList[d.index]; + const QList channelCounts = info.supportedChannelCounts(); + + layouts.clear(); + + for (QList::const_iterator ci = channelCounts.begin(); ci != channelCounts.end(); ci++) + { + LayoutsVector l = TwkAudio::channelLayouts(*ci); + for (int i = 0; i < l.size(); i++) layouts.push_back(l[i]); + } +} + +void +QTAudioRenderer::availableFormats(const Device &d, FormatVector &formats) +{ + const QAudioDeviceInfo &info = m_deviceList[d.index]; + const QList sizes = info.supportedSampleSizes(); + const QList types = info.supportedSampleTypes(); + const QList rates = info.supportedSampleRates(); + const int channelCount = TwkAudio::channelsCount(d.layout); + + formats.clear(); + + for (QList::const_iterator ci = sizes.begin(); ci != sizes.end(); ci++) + { + for (QList::const_iterator cj = types.begin(); cj != types.end(); cj++) + { + TwkAudio::Format fmt = convertToTwkAudioFormat(*ci, *cj); + + if (fmt != TwkAudio::UnknownFormat) + { + QAudioFormat f; + + f.setChannelCount(channelCount); + f.setCodec(QString(m_codec.c_str())); + f.setByteOrder(QAudioFormat::LittleEndian); + f.setSampleSize(*ci); + f.setSampleType(*cj); + + // Now we check that a supported format + // has a supported rate. + // Oddly on linux a supported format can + // have no rate thats supported! + for (size_t i = 0; i < rates.size(); i++) + { + f.setSampleRate(rates[i]); + if (info.isFormatSupported(f)) + { + formats.push_back(fmt); + break; + } + } + } + } + } +} + +// +// Sets the sample size and type in qformat for a given twkFormat. +// +void +QTAudioRenderer::setSampleSizeAndType(Layout twkLayout, + Format twkFormat, + QAudioFormat &qformat) const +{ + qformat.setChannelCount(TwkAudio::channelsCount(twkLayout)); + + switch (twkFormat) + { + case TwkAudio::Float32Format: + qformat.setSampleSize(32); + qformat.setSampleType(QAudioFormat::Float); + break; + + case TwkAudio::Int32Format: + qformat.setSampleSize(32); + qformat.setSampleType(QAudioFormat::SignedInt); + break; + + case TwkAudio::Int24Format: + qformat.setSampleSize(24); + qformat.setSampleType(QAudioFormat::SignedInt); + break; + + case TwkAudio::Int16Format: + qformat.setSampleSize(16); + qformat.setSampleType(QAudioFormat::SignedInt); + break; + + case TwkAudio::Int8Format: + qformat.setSampleSize(8); + qformat.setSampleType(QAudioFormat::SignedInt); + break; + + default: + cout << "AUDIO: format unknown" << endl; + return; + } +} + +void +QTAudioRenderer::availableRates(const Device &d, Format format, RateVector &audiorates) +{ + QAudioFormat f; + + f.setChannelCount(TwkAudio::channelsCount(d.layout)); + f.setCodec(QString(m_codec.c_str())); + f.setByteOrder(QAudioFormat::LittleEndian); + + setSampleSizeAndType(d.layout, format, f); + + const QAudioDeviceInfo &info = m_deviceList[d.index]; + QList rates = info.supportedSampleRates(); + + sort(rates.begin(), rates.end()); + + audiorates.clear(); + + for (size_t i = 0; i < rates.size(); i++) + { + f.setSampleRate(rates[i]); + if (info.isFormatSupported(f)) audiorates.push_back(rates[i]); + } +} + +// +// Check that the device supports the number +// of channels as specified by m_parameters.layout. +// +bool +QTAudioRenderer::supportsRequiredChannels(const QAudioDeviceInfo &info) const +{ +#ifdef PLATFORM_LINUX + if (AudioRenderer::debug) + { + const QList channels = info.supportedChannelCounts(); + for (QList::const_iterator ci = channels.begin(); ci != channels.end(); ci++) + TwkUtil::Log("AUDIO") << info.deviceName().toStdString() << " supports channel count = " << (*ci); + } + + // Its possible e.g. Linux that some devices hv no channel count info + // so we assume its supported... hence we always return true. + return true; +#else + const QList channels = info.supportedChannelCounts(); + + if (AudioRenderer::debug) + { + for (QList::const_iterator ci = channels.begin(); ci != channels.end(); ci++) + TwkUtil::Log("Audio") << info.deviceName().toStdString() << " supports channel count = " << (*ci); + } + + // + // Now we can support driving whatever channels the device advertizes + // even if we have to fake it by upscaling the limited channels available + // in the input sources + // + + return (!channels.empty()); +#endif +} + +void +QTAudioRenderer::initDeviceList() +{ + if (m_deviceList.empty()) + { + // + // This appears to be a bug with Linux (on centos6.5). + // Basically the size returned by availbleDevices() is different + // the first time you call it compared to all subsequent times. + // So this logic between keeps calling availableDevices() until + // its size doesnt change; we limit this to five attempts. + // + int noOfAttempts = 5; + int noOfDevices = 0; + int prev_noOfDevices = 0; + + do + { + prev_noOfDevices = noOfDevices; + noOfDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput).size(); + --noOfAttempts; + } while (prev_noOfDevices != noOfDevices && noOfAttempts); + + m_deviceList << QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); + } +} + +// The purpose of init() is to +// 1. Populate the AudioRenderer m_outputDevices(); +// 2. Initialise the current Device, m_device based on current +// preference device choice while finding the closest sample rate +// allowed for that device. +// 3. Initialise the current QAudioFormat, m_format, based of current +// preference sample size and sample rate. +// 4. Create QAudioThread. +// +void +QTAudioRenderer::init() +{ + const int channelCount = TwkAudio::channelsCount(m_parameters.layout); + + if (AudioRenderer::debug) TwkUtil::Log("AUDIO") << "init()"; + + // + // Init the device list + // + initDeviceList(); + + if (m_parameters.device == "Default" || m_parameters.device.empty()) + { + const QAudioDeviceInfo &defaultInfo = QAudioDeviceInfo::defaultOutputDevice(); + + bool validDevice = false; + + for (size_t i = 0; i < m_deviceList.size(); ++i) + { + if (m_deviceList[i].deviceName() == defaultInfo.deviceName()) + { + validDevice = true; + break; + } + } + + // Check that the defaultOutputDevice does support the required + // number of channels. + if (validDevice && supportsRequiredChannels(defaultInfo)) + { + m_parameters.device = defaultInfo.deviceName().toStdString(); + if (AudioRenderer::debug) + { + TwkUtil::Log("AUDIO") << "Using default device=" << m_parameters.device; + } + + QAudioFormat qformat; + + qformat.setSampleRate((int) m_parameters.rate); + qformat.setChannelCount(channelCount); + qformat.setCodec(QString(m_codec.c_str())); + qformat.setByteOrder(QAudioFormat::LittleEndian); + //qformat.setByteOrder(QAudioFormat::BigEndian); + + setSampleSizeAndType(m_parameters.layout, m_parameters.format, qformat); + + // + // If the default device does not support the format + // defined by m_parameters; we change to the + // preferredFormat() of the default device. + if (!defaultInfo.isFormatSupported(qformat)) + { + const QAudioFormat defaultFormat = defaultInfo.preferredFormat(); + + m_parameters.format = convertToTwkAudioFormat(defaultFormat.sampleSize(), + defaultFormat.sampleType()); + } + } + } + + m_outputDevices.clear(); + for (size_t i = 0; i < m_deviceList.size(); ++i) + { + const QAudioDeviceInfo &info = m_deviceList[i]; + + if (info.isNull() || !supportsRequiredChannels(info)) continue; + + const QAudioFormat defaultFormat = info.preferredFormat(); + + std::string deviceName = info.deviceName().toStdString(); + + // On Windows, a QAudioDevice can be listed twice with the same name + // This is due to the fact that both Qt audio plugins, wasapi and WinMM, + // can return the exact same name for an audiio output device + // https://bugreports.qt.io/browse/QTBUG-75781 + // Both will have different capabilities and RV references the output + // audio device by name, so it is important that a unique name be used + // to reference those distinct devices of the same name. + while (findDeviceByName(deviceName)!=-1) + { + deviceName+="_"+info.realm().toStdString(); + } + + Device d(deviceName); + + d.layout = m_parameters.layout; + d.defaultRate = defaultFormat.sampleRate(); + d.latencyLow = 0; + d.latencyHigh = 0; + d.index = i; + + + if (m_parameters.device == "Default" || m_parameters.device.empty()) + { + // If we got here it implies QAudioDeviceInfo::defaultOutputDevice() + // returns a device with too few channels so we cannot use it + // as our default device. Instead we pick the first device that + // supports the channel count we need. + m_parameters.device = d.name; + m_parameters.format = convertToTwkAudioFormat(defaultFormat.sampleSize(), + defaultFormat.sampleType()); + } + + if (m_parameters.device == d.name) + { + d.isDefaultDevice = true; + m_device = info; + } + else + { + d.isDefaultDevice = false; + } + + if (AudioRenderer::debug) + { + cout << "AUDIO: device=" << d.name << " with channelsCount = " << TwkAudio::channelsCount(d.layout) + << " defaultSampleSize=" << (int) defaultFormat.sampleSize() + << " defaultSampleType=" << (int) defaultFormat.sampleType() + << " defaultSampleByteOrder=" << (int) defaultFormat.byteOrder() + << " defaultrate=" << (int) d.defaultRate + << " isDefaultDevice=" << (int) d.isDefaultDevice << endl; + } + + m_outputDevices.push_back(d); + } + + if (AudioRenderer::debug) + { + cout << "AUDIO: init default device=" << m_device.deviceName().toStdString() << endl; + cout << "AUDIO: init m_parameters.device=" << m_parameters.device << endl; + cout << "AUDIO: init m_parameters.format=" << (int) m_parameters.format + << " (" << TwkAudio::formatString(m_parameters.format) << ")" << endl; + cout << "AUDIO: init m_parameters.rate=" << (int) m_parameters.rate << endl; + cout << "AUDIO: init m_parameters.layout=" << (int) m_parameters.layout + << " (" << TwkAudio::channelsCount(m_parameters.layout) << " channels)" << endl; + cout << "AUDIO: init m_parameters.framesPerBuffer=" << (int) m_parameters.framesPerBuffer << endl; + cout << "AUDIO: init m_parameters.latency=" << m_parameters.latency << endl; + cout << "AUDIO: init m_parameters.preRoll=" << (m_parameters.preRoll ? 1 : 0) << endl; + cout << "AUDIO: init m_parameters.hardwareLock=" << (m_parameters.hardwareLock ? 1 : 0) << endl; + } + + if (!m_outputDevices.empty()) + { + size_t defaultDeviceIndex = 0; + + for (size_t i = 0; i < m_outputDevices.size(); ++i) + { + if (m_outputDevices[i].isDefaultDevice) + { + defaultDeviceIndex = i; + break; + } + } + + const Device &defaultDevice = m_outputDevices[defaultDeviceIndex]; + + if (!defaultDevice.isDefaultDevice) + { + if (m_parameters.device == "Default") + { + cout << "ERROR: audio default device is NOT available." << endl; + } + else + { + // The device that was picked and registered in the preferences + // might no longer be available because its unplugged. + cout << "WARNING: audio device '" << m_parameters.device << + "' is unavailable: Using default." << endl; + m_parameters.device = "Default"; + m_parameters.layout = TwkAudio::Stereo_2; + init(); + return; + } + } + + DeviceState state; + + state.framesPerBuffer = m_parameters.framesPerBuffer; + state.format = m_parameters.format; + state.layout = defaultDevice.layout; + state.device = defaultDevice.name; + state.rate = defaultDevice.defaultRate; + state.latency = m_parameters.latency; + + // Find the best matching sample rate for the + // select device. + if (m_parameters.rate != 0.0) + { + state.rate = m_parameters.rate; + } + else + { + m_parameters.rate = state.rate; + } + + RateVector rates; + + availableRates(defaultDevice, state.format, rates); + + if (!rates.empty()) + { + double nearRate = rates[0]; + + for (size_t i = 1; i < rates.size(); i++) + { + if (fabs(rates[i] - state.rate) < fabs(nearRate - state.rate)) + { + nearRate = rates[i]; + } + } + + state.rate = nearRate; + m_parameters.rate = state.rate; + } + + // + // Init the QAudioFormat to use in m_format + // + m_format.setSampleRate((int) state.rate); + m_format.setChannelCount(TwkAudio::channelsCount(state.layout)); + m_format.setCodec(QString(m_codec.c_str())); + m_format.setByteOrder(QAudioFormat::LittleEndian); + //m_format.setByteOrder(QAudioFormat::BigEndian); + + setSampleSizeAndType(state.layout, state.format, m_format); + + if (!m_device.isFormatSupported(m_format)) + { + m_format = m_device.nearestFormat(m_format); + if (m_format.sampleType() == QAudioFormat::Unknown) + { + cout << "AUDIO: Default format not supported - trying to use nearest" << endl; + setErrorCondition("Default format not supported - trying to use nearest"); + } + else + { + if (AudioRenderer::debug) + { + cout << "AUDIO: nearest m_format.sampleType()=" << (int) m_format.sampleType() << endl; + cout << "AUDIO: nearest m_format.sampleSize()=" << (int) m_format.sampleSize() << endl; + cout << "AUDIO: nearest m_format.sampleRate()=" << (int) m_format.sampleRate() << endl; + cout << "AUDIO: nearest m_format.channelCount()=" << (int) m_format.channelCount() << endl; + } + } + + state.rate = m_parameters.rate = m_format.sampleRate(); + state.layout = m_parameters.layout = TwkAudio::channelLayouts(m_format.channelCount()).front(); + state.format = m_parameters.format = getTwkAudioFormat(); + } + +#ifdef DEBUG_QTAUDIO + if (AudioRenderer::debug) + { + cout << "AUDIO: m_format.sampleType()=" << (int) m_format.sampleType() << endl; + cout << "AUDIO: m_format.sampleSize()=" << (int) m_format.sampleSize() << endl; + cout << "AUDIO: m_format.sampleRate()=" << (int) m_format.sampleRate() << endl; + cout << "AUDIO: m_format.channelCount()=" << (int) m_format.channelCount() << endl; + } +#endif + + setDeviceState(state); + + if (AudioRenderer::debug) + { + cout << "AUDIO: init: state.format =" << (int) state.format + << " (" << TwkAudio::formatString(state.format) << ")" << endl; + cout << "AUDIO: init: state.rate =" << state.rate << endl; + cout << "AUDIO: init: state.layout =" << (int) state.layout + << " (" << TwkAudio::channelsCount(state.layout) << " channels)" << endl; + cout << "AUDIO: init: state.framesPerBuffer =" << state.framesPerBuffer << endl; + } + + // Create the audio renderer thread. + // The renderer runs in this thread. + // + m_thread = new QTAudioThread(*this, m_parent); + if (!m_thread) + { + setErrorCondition("Unable to create QThread for Platform Audio."); + } + else + { + m_thread->startMe(); + } + } + else + { + setErrorCondition("No devices available for Platform Audio."); + } +} + +void QTAudioRenderer::play() +{ + + if (m_thread) + { + m_thread->emitStartAudio(); + } + + if (AudioRenderer::debug) TwkUtil::Log("AUDIO") << "play"; + AudioRenderer::play(); +} + +void QTAudioRenderer::play(IPCore::Session* s) +{ + if (AudioRenderer::debug) TwkUtil::Log("AUDIO") << "play session"; + + s->audioVarLock(); + s->setAudioTimeShift(numeric_limits::max()); + s->setAudioStartSample(0); + s->setAudioFirstPass(true); + s->audioVarUnLock(); + + AudioRenderer::play(s); + + if (m_thread) m_thread->emitPlay(s); +} + + +void QTAudioRenderer::stop() +{ + if (AudioRenderer::debug) TwkUtil::Log("AUDIO") << "stop"; + + if (m_thread) m_thread->emitSuspendAndResetAudio(); + + AudioRenderer::stop(); +} + +void QTAudioRenderer::stop(IPCore::Session* s) +{ + if (AudioRenderer::debug) TwkUtil::Log("AUDIO") << "stop session"; + + + AudioRenderer::stop(s); + s->audioVarLock(); + s->setAudioTimeShift(numeric_limits::max()); + s->audioVarUnLock(); +} + +void +QTAudioRenderer::shutdown() +{ + if (AudioRenderer::debug) TwkUtil::Log("AUDIO") << "shutdown"; + + + if (m_thread) + { + if (!m_parameters.holdOpen) + { + // NB: You only get here if you have the "Hold + // audio device open" preference unchecked. + // + m_thread->emitStopAudio(); + } + } +} + + +} // IPCore diff --git a/src/lib/audio/QTAudioRenderer/QTAudioRenderer/QTAudioRenderer.h b/src/lib/audio/QTAudioRenderer/QTAudioRenderer/QTAudioRenderer.h new file mode 100644 index 000000000..9134590b5 --- /dev/null +++ b/src/lib/audio/QTAudioRenderer/QTAudioRenderer/QTAudioRenderer.h @@ -0,0 +1,273 @@ +//****************************************************************************** +// Copyright (c) 2013, 2014 Tweak Software. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifndef __audio__QTAudioRenderer__h__ +#define __audio__QTAudioRenderer__h__ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +namespace IPCore { + +class QTAudioThread; +class QTAudioRenderer; + + +class QTAudioIODevice : public QIODevice +{ + Q_OBJECT + + public: + QTAudioIODevice(QTAudioThread &audioThread); + virtual ~QTAudioIODevice(); + + virtual qint64 readData(char* data, qint64 maxlen); + virtual qint64 writeData(const char* data, qint64 len); + qint64 bytesAvailable() const; + + void start(); + + public slots: + void resetDevice(); + void stopDevice(); + + private: + QTAudioThread &m_thread; +}; + + +class QTAudioOutput : public QAudioOutput +{ + Q_OBJECT + + public: + QTAudioOutput(QAudioDeviceInfo &audioDeviceInfo, + QAudioFormat &audioFormat, + QTAudioIODevice &ioDevice, + QTAudioThread &audioThread); + ~QTAudioOutput(); + + + public slots: + void startAudio(); + void resetAudio(); + void stopAudio(); + void suspendAudio(); + void suspendAndResetAudio(); + void setAudioOutputBufferSize(); + void play(IPCore::Session *s); + + private: + int calcAudioBufferSize(const int channels, + const int sampleRate, + const int sampleSizeInBytes, + const int defaultBufferSize) const; + std::string toString(QAudio::State state); + + private: + QAudioDeviceInfo &m_device; + QAudioFormat &m_format; + QTAudioIODevice &m_ioDevice; + QTAudioThread &m_thread; +}; + + +class QTAudioThread : public QThread +{ + Q_OBJECT + + public: + QTAudioThread(QTAudioRenderer &audioRenderer, + QObject* parent = 0); + ~QTAudioThread(); + + void startMe(); + + size_t processedSamples() const; + void setProcessedSamples(size_t n); + + size_t startSample() const; + void setStartSample(size_t value); + + TwkAudio::Time actualPlayedTimeOffset() const; + void setActualPlayedTimeOffset(TwkAudio::Time t); + + void setDeviceLatency(double t); + + bool preRollDisable() const; + void setPreRollDisable(bool disable); + + void setPreRollDelay(TwkAudio::Time t); + + int bytesPerSample() const; + size_t framesPerBuffer() const; + + bool holdOpen() const; + + const AudioRenderer::DeviceState& deviceState() const; + void setDeviceState(AudioRenderer::DeviceState &state); + + void emitStartAudio(); + void emitResetAudio(); + void emitStopAudio(); + void emitSuspendAudio(); + void emitSuspendAndResetAudio(); + + void emitResetDevice(); + void emitStopDevice(); + + void emitPlay(IPCore::Session* s); + + // + // callback for QTAudioIODevice's readData() to call + // + qint64 qIODeviceCallback(char* data, qint64 maxLenInBytes); + + void startOfInitialization(); + TwkUtil::SystemClock::Time endOfInitialization(); + bool isInInitialization()const; + + TwkUtil::SystemClock::Time getLastDeviceLatency() const { return m_lastDeviceLatency; } + + + protected: + virtual void run(); + + private: + + bool createAudioOutput(); + + void detachAudioOutputDevice(); + + private: + QMutex m_mutex; + + QObject* m_parent; + + QTAudioIODevice* m_ioDevice; + QTAudioOutput* m_audioOutput; + TwkAudio::AudioBuffer m_abuffer; + QTAudioRenderer &m_audioRenderer; + + int m_bytesPerSample; + size_t m_preRollSamples; + size_t m_processedSamples; + size_t m_startSample; + int m_preRollMode; + bool m_preRollDisable; + + bool m_patch9355Enabled; + TwkUtil::SystemClock m_systemClock; + TwkUtil::SystemClock::Time m_startOfInitialization; + TwkUtil::SystemClock::Time m_endOfInitialization; + TwkUtil::SystemClock::Time m_lastDeviceLatency; +}; + + +class QTAudioRenderer : public IPCore::AudioRenderer +{ + public: + typedef TwkAudio::AudioBuffer AudioBuffer; + + QTAudioRenderer(const RendererParameters&, QObject* parent); + virtual ~QTAudioRenderer(); + + // + // AudioRenderer API + // + + virtual void availableLayouts(const Device&, LayoutsVector&); + virtual void availableFormats(const Device&, FormatVector&); + virtual void availableRates(const Device&, Format, RateVector&); + + // + // play() will return almost immediately -- a worker thread will + // be released and start playing. + // + + virtual void play(); + virtual void play(IPCore::Session*); + + // + // stop() will cause the worker thread to wait until play. + // + + virtual void stop(); + virtual void stop(IPCore::Session*); + + // + // shutdown() close all hardware devices + // + + virtual void shutdown(); + + // + // T is the Application type that is adding this + // renderer as an audio module e.g. 'RvApplication' + // or 'NoodleApplication'. + template + static IPCore::AudioRenderer::Module addQTAudioModule(); + + TwkAudio::Format getTwkAudioFormat() const; + TwkAudio::Format convertToTwkAudioFormat(int fmtSize, + QAudioFormat::SampleType fmtType) const; + + void setSampleSizeAndType(Layout twkLayout, + Format twkFormat, + QAudioFormat &qformat) const; + + friend class QTAudioThread; + + private: + + bool supportsRequiredChannels(const QAudioDeviceInfo &info) const; + + void init(); + + void initDeviceList(); + + private: + QObject* m_parent; + QTAudioThread* m_thread; + QAudioFormat m_format; + QAudioDeviceInfo m_device; + QList m_deviceList; + std::string m_codec; +}; + + + + +template +static IPCore::AudioRenderer* +createQTAudio(const IPCore::AudioRenderer::RendererParameters &p) +{ + return (new QTAudioRenderer(p, static_cast(IPCore::App()))); +} + +template +IPCore::AudioRenderer::Module +QTAudioRenderer::addQTAudioModule() +{ + return IPCore::AudioRenderer::Module("Platform Audio", "", createQTAudio); +} + +} // IPCore + +#endif // __audio__QTAudioRenderer__h__ diff --git a/src/lib/audio/TwkAudio/Audio.cpp b/src/lib/audio/TwkAudio/Audio.cpp new file mode 100644 index 000000000..cbf0e67c6 --- /dev/null +++ b/src/lib/audio/TwkAudio/Audio.cpp @@ -0,0 +1,297 @@ +//****************************************************************************** +// Copyright (c) 2007 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** + +#include +#include +#include +#include + +#include +#include +#include + +namespace TwkAudio { +using namespace std; + +AudioBuffer::AudioBuffer() + : m_rate(0), + m_startTime(0), + m_numSamples(0), + m_data(0), + m_margin(0) +{ +} + +AudioBuffer::AudioBuffer(size_t numSamples, + ChannelsVector channels, + Time rate, + Time startTime, + size_t margin) + : m_rate(rate), + m_startTime(startTime), + m_numSamples(numSamples), + m_data(0), + m_margin(margin), + m_channels(channels) +{ + m_buffer.resize((m_numSamples + 2 * m_margin) * m_channels.size()); + m_data = numSamples ? (&m_buffer.front() + margin * m_channels.size()) : 0; +} + +AudioBuffer::AudioBuffer(size_t numSamples, + TwkAudio::Layout layout, + Time rate, + Time startTime, + size_t margin) + : m_rate(rate), + m_startTime(startTime), + m_numSamples(numSamples), + m_data(0), + m_margin(margin), + m_channels(TwkAudio::layoutChannels(layout)) +{ + m_buffer.resize((m_numSamples + 2 * m_margin) * m_channels.size()); + m_data = numSamples ? (&m_buffer.front() + margin * m_channels.size()) : 0; +} + +AudioBuffer::AudioBuffer(Time start, + Time duration, + Time rate, + ChannelsVector channels, + size_t margin) + : m_startTime(start), + m_rate(rate), + m_numSamples(size_t(duration * rate + 0.49)), + m_margin(margin), + m_channels(channels) +{ + m_buffer.resize((m_numSamples + 2 * m_margin) * m_channels.size()); + m_data = m_numSamples ? (&m_buffer.front() + margin * m_channels.size()) : 0; +} + +AudioBuffer::AudioBuffer(BufferPointer externalMemory, + ChannelsVector channels, + size_t numSamples, + Time start, + Time rate, + size_t margin) + : m_startTime(start), + m_rate(rate), + m_data(externalMemory + margin), + m_numSamples(numSamples), + m_margin(margin), + m_channels(channels) +{ +} + +AudioBuffer::AudioBuffer(AudioBuffer& inbuffer, + size_t startOffsetSample, + size_t numSamples, + Time startTime) +{ + m_channels = inbuffer.channels(); + m_numSamples = numSamples; + m_margin = 0; + m_rate = inbuffer.rate(); + m_startTime = startTime; + m_data = inbuffer.size() + ? (inbuffer.pointer() + startOffsetSample * m_channels.size()) + : 0; +} + +void +AudioBuffer::ownData() +{ + if (!bufferOwnsData()) + { + m_buffer.resize((m_numSamples + 2 * m_margin) * m_channels.size()); + + if (!m_buffer.empty()) + { + memcpy(&m_buffer.front(), + m_data - m_margin, + sizeof(float) * m_channels.size() * (m_numSamples + 2 * m_margin)); + + m_data = &m_buffer.front() + m_margin * m_channels.size(); + } + else + { + m_data = 0; + } + + m_numSamples = m_buffer.size() / m_channels.size() - 2 * m_margin; + } +} + +void +AudioBuffer::reconfigure(size_t numSamples, + ChannelsVector channels, + Time rate, + Time startTime, + size_t margin) +{ + ownData(); + m_buffer.resize((numSamples + 2 * margin) * channels.size()); + + m_channels = channels; + m_numSamples = numSamples; + m_rate = rate; + m_startTime = startTime; + m_margin = margin; + m_data = numSamples ? (&m_buffer.front() + margin * m_channels.size()) : 0; +} + +void +AudioBuffer::reconfigure(size_t numSamples, + TwkAudio::Layout layout, + Time rate, + Time startTime, + size_t margin) +{ + return reconfigure(numSamples, + layoutChannels(layout), + rate, + startTime, + margin); +} + +void +AudioBuffer::zero() +{ + if (m_data) memset(m_data - m_margin * m_channels.size(), 0, sizeInBytesIncludingMargin()); +} + +void +AudioBuffer::zeroRegion(size_t start, size_t n) +{ + if (!m_data) return; + assert(n <= (m_numSamples - start)); + + size_t a = (m_margin + start) * m_channels.size(); + memset(m_data + a, 0, n * m_channels.size() * sizeof(SampleType)); +} + +bool +AudioBuffer::bufferOwnsData() const +{ + if (m_buffer.empty()) + { + return m_data == 0; + } + else + { + return m_data == (&m_buffer.front() + m_channels.size() * m_margin); + } +} + + +void +AudioBuffer::reverse(bool reverseChannels) +{ + float* data = m_data; + + if (reverseChannels) + { + std::reverse(data, data + m_numSamples * m_channels.size()); + std::reverse(m_channels.begin(), m_channels.end()); + } + else + { + size_t halfNumSamples = m_numSamples/2; + + for (size_t i=0; i < halfNumSamples; ++i) + { + float* out0 = data + i * m_channels.size(); + float* out1 = data + (m_numSamples - 1 - i) * m_channels.size(); + + for (size_t j=0; j < m_channels.size(); ++j) + { + float tmp = out0[j]; + out0[j] = out1[j]; + out1[j] = tmp; + } + } + } +} + +bool +AudioBuffer::checkBuffer(const char* label) const +{ + size_t zeroCount = 0; + size_t outOfBoundsCount = 0; + size_t maxCount = 0; + size_t gapCount = 0; + const float* data = m_data; + const size_t numOfFloats = m_numSamples * m_channels.size(); + + float maxValue = -std::numeric_limits::max(); + float minValue = std::numeric_limits::max(); + + for (size_t i=0; i < numOfFloats; ++i) + { + if (data[i] == 0.0f) + { + if (zeroCount == 0) + { + ++gapCount; + } + + ++zeroCount; + } + else + { + if (zeroCount > maxCount) + { + maxCount = zeroCount; + } + zeroCount = 0; + } + + if ((data[i] > 1.0f) || (data[i] < -1.0f) ) + { + if (data[i] > maxValue) + { + maxValue = data[i]; + } + else if (data[i] < minValue) + { + minValue = data[i]; + } + + ++outOfBoundsCount; + } + + + } + + if (maxCount > 1 && m_startTime != 0.0) + { + cerr << "Time=" << m_startTime << " : AudioBuffer::check() called from " << label << ": " + << "zeroCount=" << maxCount + << " gapCount=" << gapCount + << endl; + + return false; + } + + + if (outOfBoundsCount) + { + cerr << "Time=" << m_startTime << " : AudioBuffer::check() called from " << label << ": " + << "outOfBoundsCount=" << outOfBoundsCount + << "maxValue=" << maxValue + << "minValue=" << minValue + << endl; + + return false; + } + + return true; +} + +} // TwkAudio diff --git a/src/lib/audio/TwkAudio/AudioCache.cpp b/src/lib/audio/TwkAudio/AudioCache.cpp new file mode 100644 index 000000000..d2306e807 --- /dev/null +++ b/src/lib/audio/TwkAudio/AudioCache.cpp @@ -0,0 +1,384 @@ +//****************************************************************************** +// Copyright (c) 2008 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** + +#include +#include +#include +#include +#include +#include +#include + +namespace TwkAudio { +using namespace std; + +AudioCache::AudioCache() + : m_packetSize(512), + m_packetLayout(TwkAudio::Stereo_2), + m_count(0), + m_packetRate(0), + m_totalSecondsCached(0), + m_cachedRangesStatFrameRate(0.0) +{ + pthread_mutex_init(&m_lock, 0); +} + +AudioCache::~AudioCache() +{ + clear(); + pthread_mutex_destroy(&m_lock); +} + +void +AudioCache::clear() +{ + for (PacketMap::iterator i = m_map.begin(); + i != m_map.end(); + ++i) + { + //delete [] i->second; + TwkUtil::MemPool::dealloc(i->second); + } + + for (size_t i = 0; i < m_freePackets.size(); i++) + { + TwkUtil::MemPool::dealloc(m_freePackets[i]); + } + + m_freePackets.clear(); + m_map.clear(); + m_count = 0; + m_totalSecondsCached = 0; + m_cachedRangesStat.clear(); +} + +void +AudioCache::clearBefore(Time t) +{ + size_t s = sampleAtTime(t); + + PacketMap::iterator a = m_map.end(); + PacketMap::iterator b = a; + + for (PacketMap::iterator i = m_map.begin(); + i != m_map.end(); + ++i) + { + if (i->first < s) + { + m_freePackets.push_back(i->second); + m_count--; + i->second = 0; + if (a == m_map.end()) a = i; + } + else if (i->first >= s) + { + b = i; + break; + } + } + + if (a != b && a != m_map.end()) + { + m_map.erase(a, b); + m_cachedRangesStat.clear(); + } + + m_totalSecondsCached = Time(m_count) * Time(m_packetSize) / m_packetRate; +} + +void +AudioCache::clearAfter(Time t) +{ + SampleTime s = sampleAtTime(t); + + PacketMap::iterator a = m_map.end(); + PacketMap::iterator b = a; + + vector tbd; + + for (PacketMap::iterator i = m_map.begin(); + i != m_map.end(); + ++i) + { + if (i->first > s) + { + m_freePackets.push_back(i->second); + m_count--; + i->second = 0; + if (a == m_map.end()) a = i; + } + } + + if (a != b && a != m_map.end()) + { + m_map.erase(a, b); + m_cachedRangesStat.clear(); + } + + m_totalSecondsCached = Time(m_count) * Time(m_packetSize) / m_packetRate; +} + +void +AudioCache::clear(Time time0, Time time1) +{ + SampleTime s0 = sampleAtTime(time0); + SampleTime s1 = sampleAtTime(time1); + + PacketMap::iterator a = m_map.end(); + PacketMap::iterator b = a; + + vector tbd; + + for (PacketMap::iterator i = m_map.begin(); + i != m_map.end(); + ++i) + { + if ( i->first > s0 ) + { + if ( i->first < s1 ) + { + m_freePackets.push_back( i->second ); + m_count--; + i->second = 0; + if ( a == m_map.end() ) a = i; + } + else + { + b = i; + break; + } + } + } + + if (a != b && a != m_map.end()) + { + m_map.erase(a, b); + m_cachedRangesStat.clear(); + } + + m_totalSecondsCached = Time(m_count) * Time(m_packetSize) / m_packetRate; +} + +void +AudioCache::configurePacket(size_t samples, + TwkAudio::Layout layout, + Time rate) +{ + if (m_packetSize != samples || + m_packetLayout != layout || + m_packetRate != rate) + { + clear(); + m_packetSize = samples; + m_packetLayout = layout; + m_packetRate = rate; + } +} + +const AudioCache::Packet +AudioCache::find(SampleTime s) const +{ + s -= packetOffset(s); + + PacketMap::const_iterator i = m_map.find(s); + + if (i != m_map.end()) + { + return i->second; + } + else + { + return 0; + } +} + +void +AudioCache::add(const AudioBuffer& buffer) +{ + Time t = buffer.startTime(); + SampleTime s = sampleAtTime(t); + + // + // Since all samples in the cache are stored in packet size clumps we must + // make sure that this sample set starts exactly on a packet boundary, + // is the right amount of samples and channels, and is at the correct + // sampling rate. + // + + if (packetOffset(s) != 0 || + buffer.size() != m_packetSize || + buffer.channels() != channels() || + buffer.rate() != m_packetRate) + { + cout << "WARNING: s = " << s + << ", packetOffset = " << packetOffset(s) + << ", m_packetSize = " << m_packetSize + << ", size = " << buffer.size() + << ", m_packetLayout = " << TwkAudio::layoutString(m_packetLayout) + << ", channels = " << buffer.numChannels() + << ", m_packetRate = " << m_packetRate + << ", rate: " << buffer.rate() + << endl; + } + assert(packetOffset(s) == 0); + assert(buffer.size() == m_packetSize); + assert(buffer.channels() == channels()); + assert(buffer.rate() == m_packetRate); + + if (!find(s)) + { + unlock(); + Packet p = 0; + + if (m_freePackets.empty()) + { + int numChannels = TwkAudio::channelsCount(m_packetLayout); + p = (float*) TwkUtil::MemPool::alloc(sizeof(float) * m_packetSize * numChannels); + } + else + { + p = m_freePackets.back(); + m_freePackets.pop_back(); + } + + memcpy(p, buffer.pointer(), buffer.sizeInBytes()); + lock(); + + m_map[s] = p; + m_count++; + m_totalSecondsCached = Time(m_count) * Time(m_packetSize) / m_packetRate; + m_cachedRangesStat.clear(); + } + else + { + cout << "dup @ " << s << endl; + } +} + +bool +AudioCache::fillBuffer(AudioBuffer& buffer) const +{ + const SampleTime s0 = sampleAtTime(buffer.startTime()); + const SampleTime d0 = sampleAtTime(buffer.duration()); + const SampleTime e0 = s0 + d0 - 1; + int numChannels = TwkAudio::channelsCount(m_packetLayout); + + //assert(d0 == m_packetSize); + + if (d0 <= m_packetSize) + { + const Packet p0 = find(s0); + const Packet p1 = find(e0); + + if (p0 && p0 == p1) + { + // + // This should be the most common case: the buffer range is + // completely contained inside one packet. + // + + memcpy(buffer.pointer(), + p0 + packetOffset(s0) * numChannels, + buffer.sizeInBytes()); + + return true; + } + else if (p0 && p1) + { + // + // Straddles two packets + // + + const SampleTime n0 = SampleTime(m_packetSize) - packetOffset(s0); + const SampleTime n1 = packetOffset(e0) + 1; + + memcpy(buffer.pointer(), + p0 + packetOffset(s0) * numChannels, + n0 * sizeof(float) * numChannels); + + memcpy(buffer.pointer() + n0 * numChannels, + p1, + n1 * sizeof(float) * numChannels); + + return true; + } + } + else + { + const SampleTime total = buffer.size(); + const SampleTime n = total / SampleTime(m_packetSize); + const SampleTime r = total % SampleTime(m_packetSize); + + for (size_t i = 0; i < n; i++) + { + size_t offset = i * m_packetSize; + + AudioBuffer newBuffer(buffer.pointer() + offset * buffer.numChannels(), + buffer.channels(), + m_packetSize, + buffer.startTime() + double(i * m_packetSize) / m_packetRate, + m_packetRate); + + if (!fillBuffer(newBuffer)) return false; + } + + if (r) + { + SampleTime offset = n * SampleTime(m_packetSize); + + AudioBuffer newBuffer(buffer.pointer() + offset * buffer.numChannels(), + buffer.channels(), + r, + buffer.startTime() + double(n * m_packetSize) / m_packetRate, + m_packetRate); + + if (!fillBuffer(newBuffer)) return false; + } + + return true; + } + + return false; +} + +//------------------------------------------------------------------------------ +// +void +AudioCache::computeCachedRangesStat(double frameRate, FrameRangeVector& array) +{ + lock(); + + if (frameRate != m_cachedRangesStatFrameRate) + { + m_cachedRangesStatFrameRate = frameRate; + m_cachedRangesStat.clear(); + } + + if (m_cachedRangesStat.empty()) + { + for (PacketMap::iterator i = m_map.begin(); + i != m_map.end(); + ++i) + { + const SampleTime s = i->first; + const Time t = samplesToTime(s, m_packetRate); + const Time duration = Time(m_packetSize) / m_packetRate; + const int startFrame = ROUND(t * frameRate); + const int endFrame = ROUND((t + duration ) * frameRate); + + m_cachedRangesStat.push_back(make_pair(startFrame, endFrame)); + } + } + + array = m_cachedRangesStat; + + unlock(); +} + +} // TwkAudio diff --git a/src/lib/audio/TwkAudio/AudioFormats.cpp b/src/lib/audio/TwkAudio/AudioFormats.cpp new file mode 100644 index 000000000..117e4d135 --- /dev/null +++ b/src/lib/audio/TwkAudio/AudioFormats.cpp @@ -0,0 +1,762 @@ +//****************************************************************************** +// Copyright (c) 2015 Autodesk Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#include + +#include +#include +#include + +namespace TwkAudio { + + +bool +ChannelsVector::hasAllChannels(const ChannelsVector &b) const +{ + const ChannelsVector &a = *this; + const size_t asize = a.size(); + const size_t bsize = b.size(); + if (asize != bsize) return false; + else + { + for (int ch = 0; ch < asize; ch++) + { + ChannelsVector::const_iterator it; + it = find (b.begin(), b.end(), a[ch]); + if (it == b.end()) + { + return false; + } + } + } + return true; +} + +bool +ChannelsVector::identical(const ChannelsVector &a, const ChannelsVector &b) const +{ + const size_t asize = a.size(); + const size_t bsize = b.size(); + if (asize != bsize) return false; + return (memcmp(&a.front(), &b.front(), asize * sizeof(Channels))?false:true); +} + + +std::string +formatString(Format f) +{ + std::string fstr; + switch (f) + { + case Float32Format: fstr = "32 bit float"; break; + case Int32Format: fstr = "32 bit"; break; + case Int24Format: fstr = "24 bit"; break; + default: + case Int16Format: fstr = "16 bit"; break; + case Int8Format: fstr = "8 bit"; break; + case UInt32_SMPTE272M_20Format: fstr = "20 bit (NV/SMPTE272M)"; break; + case UInt32_SMPTE299M_24Format: fstr = "24 bit (NV/SMPTE299M)"; break; + } + return fstr; +} + +size_t +formatSizeInBytes(Format f) +{ + size_t fsize; + switch (f) + { + case Float32Format: fsize = sizeof(float); break; + case Int32Format: fsize = sizeof(int); break; + case Int24Format: fsize = sizeof(char) * 3; break; + default: + case Int16Format: fsize = sizeof(short); break; + case Int8Format: fsize = sizeof(signed char); break; + case UInt32_SMPTE272M_20Format: fsize = sizeof(unsigned int); break; + case UInt32_SMPTE299M_24Format: fsize = sizeof(unsigned int); break; + } + return fsize; +} + +std::string +channelString(Channels channel) +{ + std::string chstr; + switch (channel) + { + case FrontLeft: chstr = "FL: front left"; break; + case FrontRight: chstr = "FR: front right"; break; + case FrontCenter: chstr = "FC: front center"; break; + case LowFrequency: chstr = "LF: low frequency"; break; + case BackLeft: chstr = "BL: back left"; break; + case BackRight: chstr = "BR: back right"; break; + case FrontLeftOfCenter: chstr = "FLC: front left center"; break; + case FrontRightOfCenter: chstr = "FRC: front right center"; break; + case BackCenter: chstr = "BC: back center"; break; + case SideLeft: chstr = "SL: side left"; break; + case SideRight: chstr = "SR: side right"; break; + case LeftHeight: chstr = "LH: left height"; break; + case RightHeight: chstr = "RH: right height"; break; + case Channel14: chstr = "CH14: channel 14"; break; + case Channel15: chstr = "CH15: channel 15"; break; + case Channel16: chstr = "CH16: channel 16"; break; + case UnknownLayout: + default: chstr = "UKN: Unknown"; break; + } + return chstr; +} + +std::string +layoutString(Layout layout) +{ + std::string chstr; + switch (layout) + { + case Mono_1: chstr = "Mono"; break; + case Stereo_2: chstr = "Stereo"; break; + case Stereo_2_1: chstr = "2.1"; break; + case Quad_4_0: chstr = "Quadrophonic"; break; + case Surround_4_1: chstr = "4.1"; break; + case Generic_4_1: chstr = "4.1 (Swap)"; break; + case Surround_5_1: chstr = "5.1"; break; + case Back_5_1: chstr = "5.1 (Back)"; break; + case Generic_5_1: chstr = "5.1 (Swap)"; break; + case AC3_5_1: chstr = "5.1 (AC3)"; break; + case DTS_5_1: chstr = "5.1 (DTS)"; break; + case AIFF_5_1: chstr = "5.1 (AIFF)"; break; + case Generic_6_1: chstr = "6.1"; break; + case SDDS_7_1: chstr = "7.1 (SDDS)"; break; + case Surround_7_1: chstr = "7.1"; break; + case Back_7_1: chstr = "7.1 (Back)"; break; + case Surround_9_1: chstr = "9.1"; break; + case Generic_16: chstr = "16"; break; + case UnknownLayout: + default: chstr = "Unknown"; break; + } + return chstr; +} + +LayoutsVector +channelLayouts(int channelCount) +{ + LayoutsVector lv; + switch (channelCount) + { + case 1: lv.push_back(Mono_1); break; + case 2: lv.push_back(Stereo_2); break; + case 3: lv.push_back(Stereo_2_1); break; + case 4: lv.push_back(Quad_4_0); break; + case 5: lv.push_back(Surround_4_1); + lv.push_back(Generic_4_1); break; + case 6: lv.push_back(Surround_5_1); + lv.push_back(Back_5_1); + lv.push_back(Generic_5_1); + lv.push_back(AC3_5_1); + lv.push_back(DTS_5_1); + lv.push_back(AIFF_5_1); break; + case 7: lv.push_back(Generic_6_1); break; + case 8: lv.push_back(Surround_7_1); + lv.push_back(Back_7_1); break; + lv.push_back(SDDS_7_1); break; + case 10: lv.push_back(Surround_9_1); break; + case 16: lv.push_back(Generic_16); break; + case 0: + default: lv.push_back(UnknownLayout); break; + } + return lv; +} + +Layout +channelLayout(const ChannelsVector &cv) +{ + if (cv == layoutChannels(Stereo_2)) + { + return Stereo_2; + } + else if (cv == layoutChannels(Surround_5_1)) + { + return Surround_5_1; + } + else if (cv == layoutChannels(Surround_7_1)) + { + return Surround_7_1; + } + else if (cv == layoutChannels(Back_5_1)) + { + return Back_5_1; + } + else if (cv == layoutChannels(Back_7_1)) + { + return Back_7_1; + } + else if (cv == layoutChannels(Generic_5_1)) + { + return Generic_5_1; + } + else if (cv == layoutChannels(AC3_5_1)) + { + return AC3_5_1; + } + else if (cv == layoutChannels(DTS_5_1)) + { + return DTS_5_1; + } + else if (cv == layoutChannels(AIFF_5_1)) + { + return AIFF_5_1; + } + else if (cv == layoutChannels(Mono_1)) + { + return Mono_1; + } + else if (cv == layoutChannels(Quad_4_0)) + { + return Quad_4_0; + } + else if (cv == layoutChannels(Surround_4_1)) + { + return Surround_4_1; + } + else if (cv == layoutChannels(Generic_4_1)) + { + return Generic_4_1; + } + else if (cv == layoutChannels(SDDS_7_1)) + { + return SDDS_7_1; + } + else if (cv == layoutChannels(Generic_16)) + { + return Generic_16; + } + + + return UnknownLayout; +} + +int +channelsCount (Layout layout) +{ + int count; + switch (layout) + { + case Mono_1: count = 1; break; + case Stereo_2: count = 2; break; + case Stereo_2_1: count = 3; break; + case Quad_4_0: count = 4; break; + case Surround_4_1: count = 5; break; + case Generic_4_1: count = 5; break; + case Surround_5_1: count = 6; break; + case Back_5_1: count = 6; break; + case Generic_5_1: count = 6; break; + case AC3_5_1: count = 6; break; + case DTS_5_1: count = 6; break; + case AIFF_5_1: count = 6; break; + case Generic_6_1: count = 7; break; + case Surround_7_1: count = 8; break; + case SDDS_7_1: count = 8; break; + case Back_7_1: count = 8; break; + case Surround_9_1: count = 10; break; + case Generic_16: count = 16; break; + case UnknownLayout: + default: + std::cout << "AUDIO: channels format unsupported: Layout=" << (int) layout << std::endl; + count = 0; break; + } + return count; +} + +ChannelsVector +layoutChannels(Layout layout) +{ + ChannelsVector chv; + switch (layout) + { + case Mono_1: + chv.push_back(FrontCenter); + break; + case Stereo_2: + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + break; + case Stereo_2_1: + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(LowFrequency); + break; + case Quad_4_0: + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(BackLeft); + chv.push_back(BackRight); + break; + case Surround_4_1: + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(FrontCenter); + chv.push_back(LowFrequency); + chv.push_back(BackCenter); + break; + case Generic_4_1: + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(BackLeft); + chv.push_back(BackRight); + chv.push_back(LowFrequency); + break; + case Back_5_1: + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(FrontCenter); + chv.push_back(LowFrequency); + chv.push_back(BackLeft); + chv.push_back(BackRight); + break; + case Surround_5_1: + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(FrontCenter); + chv.push_back(LowFrequency); + chv.push_back(SideLeft); + chv.push_back(SideRight); + break; + case Generic_5_1: + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(BackLeft); + chv.push_back(BackRight); + chv.push_back(FrontCenter); + chv.push_back(LowFrequency); + break; + case AC3_5_1: + chv.push_back(FrontLeft); + chv.push_back(FrontCenter); + chv.push_back(FrontRight); + chv.push_back(SideLeft); + chv.push_back(SideRight); + chv.push_back(LowFrequency); + break; + case DTS_5_1: + chv.push_back(FrontCenter); + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(SideLeft); + chv.push_back(SideRight); + chv.push_back(LowFrequency); + break; + case AIFF_5_1: + chv.push_back(FrontLeft); + chv.push_back(BackLeft); + chv.push_back(FrontCenter); + chv.push_back(FrontRight); + chv.push_back(BackRight); + chv.push_back(LowFrequency); + break; + case Generic_6_1: + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(FrontCenter); + chv.push_back(LowFrequency); + chv.push_back(BackLeft); + chv.push_back(BackRight); + chv.push_back(BackCenter); + break; + case Surround_7_1: + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(FrontCenter); + chv.push_back(LowFrequency); + chv.push_back(SideLeft); + chv.push_back(SideRight); + chv.push_back(BackLeft); + chv.push_back(BackRight); + break; + case SDDS_7_1: + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(FrontCenter); + chv.push_back(LowFrequency); + chv.push_back(SideLeft); + chv.push_back(SideRight); + chv.push_back(FrontLeftOfCenter); + chv.push_back(FrontRightOfCenter); + break; + case Back_7_1: + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(FrontCenter); + chv.push_back(LowFrequency); + chv.push_back(BackLeft); + chv.push_back(BackRight); + chv.push_back(SideLeft); + chv.push_back(SideRight); + break; + case Surround_9_1: + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(FrontCenter); + chv.push_back(LowFrequency); + chv.push_back(BackLeft); + chv.push_back(BackRight); + chv.push_back(SideLeft); + chv.push_back(SideRight); + chv.push_back(LeftHeight); + chv.push_back(RightHeight); + break; + case Generic_16: // Not sure? + chv.push_back(FrontLeft); + chv.push_back(FrontRight); + chv.push_back(FrontCenter); + chv.push_back(LowFrequency); + chv.push_back(BackLeft); + chv.push_back(BackRight); + chv.push_back(SideLeft); + chv.push_back(SideRight); + chv.push_back(LeftHeight); + chv.push_back(RightHeight); + chv.push_back(FrontLeftOfCenter); + chv.push_back(FrontRightOfCenter); + chv.push_back(BackCenter); + chv.push_back(Channel14); + chv.push_back(Channel15); + chv.push_back(Channel16); + break; + + case UnknownLayout: + default: + break; + } + return chv; +} + +void +initChannelsMap(const ChannelsVector &a, const ChannelsVector &b, ChannelsMap &chmap) +{ + chmap.clear(); + + if (a == b) + { + // Just copy the input to the output. + for (int ch = 0; ch < b.size(); ch++) + { + ChannelMixState mix; + ChannelState state; + state.index = ch; + if ((a[ch] & Center) == Center) + { + state.weight = 0.5f; + mix.lefts.push_back(state); + mix.rights.push_back(state); + } + else if (b[ch] & Left) + { + state.weight = 1.0f; + mix.lefts.push_back(state); + } + else + { + state.weight = 1.0f; + mix.rights.push_back(state); + } + chmap[b[ch]] = mix; + } + } + else if (a.size() == b.size()) + { + if (b.hasAllChannels(a)) + { + // The order of channels are mismtached + for (int bch = 0; bch < b.size(); bch++) + { + for (int ach = 0; ach < a.size(); ach++) + { + if (a[ach] == b[bch]) + { + ChannelMixState mix; + ChannelState state; + state.index = ach; + if ((a[ach] & Center) == Center) + { + state.weight = 0.5f; + mix.lefts.push_back(state); + mix.rights.push_back(state); + } + else if (a[ach] & Left) + { + state.weight = 1.0f; + mix.lefts.push_back(state); + } + else + { + state.weight = 1.0f; + mix.rights.push_back(state); + } + chmap[b[bch]] = mix; + break; + } + } + } + } + else + { + // Same number of channels but not all channels + // are the same. In this case we just copy the + // input to the output. + for (int ch = 0; ch < b.size(); ch++) + { + ChannelMixState mix; + ChannelState state; + state.index = ch; + if ((a[ch] & Center) == Center) + { + state.weight = 0.5f; + mix.lefts.push_back(state); + mix.rights.push_back(state); + } + else if (a[ch] & Left) + { + state.weight = 1.0f; + mix.lefts.push_back(state); + } + else + { + state.weight = 1.0f; + mix.rights.push_back(state); + } + chmap[b[ch]] = mix; + } + } + } + else + { + // Mix up or down channel cases. + // + // Cases: + // 1. output=stereo input=anything; use mix down weight + // 2. output=mono input=anything; use mix down weight + // 3. output=n n > 2; matched channel assignment only + + if (b.size() == 2) // Stereo Output Case + { + float normalizeFactor = 0.0f; + for (int ach = 0; ach < a.size(); ach++) + { + normalizeFactor += LEFT_CHANNEL_WEIGHT(a[ach]); + } + + for (int bch = 0; bch < b.size(); bch++) + { + ChannelMixState mix; + if ((b[bch] & Center) == Center) + { + for (int ach = 0; ach < a.size(); ach++) + { + float weight = LEFT_CHANNEL_WEIGHT(a[ach]); + if (weight > 0) + { + ChannelState state; + state.index = ach; + state.weight = weight / normalizeFactor; + mix.lefts.push_back(state); + } + + weight = RIGHT_CHANNEL_WEIGHT(a[ach]); + if (weight > 0) + { + ChannelState state; + state.index = ach; + state.weight = weight / normalizeFactor; + mix.rights.push_back(state); + } + } + } + else if (b[bch] & Left) + { + for (int ach = 0; ach < a.size(); ach++) + { + float weight = LEFT_CHANNEL_WEIGHT(a[ach]); + if (weight > 0) + { + ChannelState state; + state.index = ach; + state.weight = weight / normalizeFactor; + mix.lefts.push_back(state); + } + } + } + else + { + for (int ach = 0; ach < a.size(); ach++) + { + float weight = RIGHT_CHANNEL_WEIGHT(a[ach]); + if (weight > 0) + { + ChannelState state; + state.index = ach; + state.weight = weight / normalizeFactor; + mix.rights.push_back(state); + } + } + } + + chmap[b[bch]] = mix; + } + } + else if (b.size() == 1) // Mono Output Case + { + ChannelMixState mix; + + float normalizeFactor = 0.0f; + for (int ach = 0; ach < a.size(); ach++) + { + normalizeFactor += LEFT_CHANNEL_WEIGHT(a[ach]) + + RIGHT_CHANNEL_WEIGHT(a[ach]); + } + + for (int ach = 0; ach < a.size(); ach++) + { + float weight = LEFT_CHANNEL_WEIGHT(a[ach]); + if (weight > 0) + { + ChannelState state; + state.index = ach; + state.weight = weight / normalizeFactor; + mix.lefts.push_back(state); + } + + weight = RIGHT_CHANNEL_WEIGHT(a[ach]); + if (weight > 0) + { + ChannelState state; + state.index = ach; + state.weight = weight / normalizeFactor; + mix.rights.push_back(state); + } + } + chmap[b[0]] = mix; + } + else + { + // N output channel mix down or mix up case. N > 2 + + ChannelMixState mixFrontCenter; + ChannelMixState mixFrontLeft; + ChannelMixState mixFrontRight; + + for (int bch = 0; bch < b.size(); bch++) + { + bool foundMatch = false; + + for (int ach = 0; ach < a.size(); ach++) + { + if ((a[ach] == FrontLeft) && mixFrontCenter.lefts.empty() ) + { + ChannelState state; + state.index = ach; + state.weight = 1.0f; + mixFrontCenter.lefts.push_back(state); + } + else if ((a[ach] == FrontRight) && mixFrontCenter.rights.empty() ) + { + ChannelState state; + state.index = ach; + state.weight = 1.0f; + mixFrontCenter.rights.push_back(state); + } + else if (a[ach] == FrontCenter) + { + if (mixFrontLeft.lefts.empty() ) + { + ChannelState state; + state.index = ach; + state.weight = 1.0f; + mixFrontLeft.lefts.push_back(state); + } + if (mixFrontRight.rights.empty() ) + { + ChannelState state; + state.index = ach; + state.weight = 1.0f; + mixFrontRight.rights.push_back(state); + } + } + + if (a[ach] == b[bch]) + { + ChannelMixState mix; + ChannelState state; + state.index = ach; + if ((a[ach] & Center) == Center) + { + state.weight = 0.5f; + mix.lefts.push_back(state); + mix.rights.push_back(state); + } + else if (a[ach] & Left) + { + state.weight = 1.0f; + mix.lefts.push_back(state); + } + else + { + state.weight = 1.0f; + mix.rights.push_back(state); + } + chmap[b[bch]] = mix; + foundMatch = true; + break; + } + } + + if (!foundMatch) + { + if ( (b[bch] == FrontCenter) && + (mixFrontCenter.lefts.size() == 1) && + (mixFrontCenter.rights.size() == 1) ) + { + // + // We define an unmatched output FrontCenter channel + // as a combination of the input FrontLeft channel + // and FrontRight iff there wasnt an input FrontCenter channel. + // + chmap[FrontCenter] = mixFrontCenter; + continue; + } + + if (b[bch] == FrontLeft) + { + // + // We define an unmatched output FrontLeft channel + // as the left portion of an input FrontCenter channel + // iff there wasnt an input FrontLeft channel. + // + chmap[FrontLeft] = mixFrontLeft; + continue; + } + + if (b[bch] == FrontRight) + { + // + // We define an unmatched output FrontRight channel + // as the right portion of an input FrontCenter + // iff there wasnt an input FrontRight channel. + // + chmap[FrontRight] = mixFrontRight; + continue; + } + + ChannelMixState mixEmpty; + chmap[b[bch]] = mixEmpty; + } + } + } + } +} + +} // TwkAudio + + diff --git a/src/lib/audio/TwkAudio/CMakeLists.txt b/src/lib/audio/TwkAudio/CMakeLists.txt new file mode 100644 index 000000000..8ea4797cb --- /dev/null +++ b/src/lib/audio/TwkAudio/CMakeLists.txt @@ -0,0 +1,51 @@ +# +# Copyright (C) 2022 Autodesk, Inc. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +# + +INCLUDE(cxx_defaults) + +SET(_target + "TwkAudio" +) + +SET(_sources + Audio.cpp + AudioCache.cpp + AudioFormats.cpp + Interlace.cpp + Resampler.cpp + Mix.cpp + Filters.cpp + ScaleTime.cpp +) + +ADD_LIBRARY( + ${_target} SHARED + ${_sources} +) + +TARGET_INCLUDE_DIRECTORIES( + ${_target} + PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} +) + +TARGET_LINK_LIBRARIES( + ${_target} + PUBLIC TwkMath stl_ext + PRIVATE TwkUtil resample +) + +IF(RV_TARGET_WINDOWS) + TARGET_COMPILE_OPTIONS( + ${_target} + PRIVATE "-DTWKAUDIO_BUILD" + ) + TARGET_LINK_LIBRARIES( + ${_target} + PUBLIC win_posix win_pthreads + ) +ENDIF() + +RV_STAGE(TYPE "SHARED_LIBRARY" TARGET ${_target}) diff --git a/src/lib/audio/TwkAudio/Filters.cpp b/src/lib/audio/TwkAudio/Filters.cpp new file mode 100644 index 000000000..5dfb80939 --- /dev/null +++ b/src/lib/audio/TwkAudio/Filters.cpp @@ -0,0 +1,93 @@ +// +// Copyright (c) 2008 Tweak Software. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +#include +#include + +namespace TwkAudio { +using namespace std; + +void +lowPassFilter(AudioBuffer& inBuffer, + AudioBuffer& prevBuffer, + AudioBuffer& outBuffer, + float freq, + bool isBackwards) +{ + // + // Simple low-pass, seeded with outPrevious's last sample + // + + // f = 1 / (2 * pi * R * C) --- where R = resistance, C = capacitance + // alpha = dt / (R * C + dt) + // dt = 1 / rate + // + // so: + // + // alpha = dt / ( 1 / (f * 2 * pi) + dt ) + // + + assert(inBuffer.rate() == outBuffer.rate() && + inBuffer.rate() == prevBuffer.rate()); + + const double dt = 1.0 / inBuffer.rate(); + const float alpha = float(dt / ( 1.0 / (double(freq) * 2 * 3.14159265359) + dt)); + + // Reverse the buffers on backwards playback + // otherwise the buffer segments will be discontinuous + // at the boundaries. + if (isBackwards) + { + inBuffer.reverse(); + prevBuffer.reverse(); + } + + AudioBuffer::BufferPointer out = outBuffer.pointer(); + const AudioBuffer::BufferPointer in = inBuffer.pointer(); + const AudioBuffer::BufferPointer prev = prevBuffer.pointer(); + const size_t nc = outBuffer.numChannels(); + const size_t n = outBuffer.size(); + const size_t pend = (prevBuffer.size() - 1) * nc; + + // + // Seed the first value + // + + for (size_t c = 0; c < nc; c++) + { + const float p = prev[pend + c]; + out[c] = p + alpha * (in[c] - p); + } + + // + // Do the rest + // + + for (size_t i = 1; i < n; i++) + { + for (size_t c = 0; c < nc; c++) + { + const size_t q = i * nc + c; + const float p = out[q-nc]; + + out[q] = p + alpha * (in[q] - p); + } + } + + if (isBackwards) + { + inBuffer.reverse(); + prevBuffer.reverse(); + if (in != out) + { + outBuffer.reverse(); + } + } +} + + +} // TwkAudio diff --git a/src/lib/audio/TwkAudio/Interlace.cpp b/src/lib/audio/TwkAudio/Interlace.cpp new file mode 100644 index 000000000..c94c807ce --- /dev/null +++ b/src/lib/audio/TwkAudio/Interlace.cpp @@ -0,0 +1,167 @@ +//****************************************************************************** +// Copyright (c) 2007 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** + +#include +#include +#include +#include + +namespace TwkAudio { +using namespace std; + + +void +deinterlace(const float* inbuffer, + size_t inSamples, + int nchannels, + vector& outbuffers) +{ + outbuffers.resize(nchannels); + + for (unsigned int i=0; i < nchannels; i++) + { + outbuffers[i].resize(inSamples); + } + + // + // De-interlace + // + + const float* inp = inbuffer; + const float* endp = inp + (inSamples * nchannels); + + for (unsigned int c = 0; c < nchannels; c++) + { + float* o = &(outbuffers[c].front()); + + for (const float* p = inp + c; p < endp; p += nchannels, o++) + { + *o = *p; + } + } +} + +void +deinterlace(const SampleVector& inbuffer, + int nchannels, + vector& outbuffers) +{ + outbuffers.resize(nchannels); + + for (unsigned int i=0; i < outbuffers.size(); i++) + { + outbuffers[i].resize(inbuffer.size() / nchannels); + } + + // + // De-interlace + // + + const float* inp = &(inbuffer.front()); + const float* endp = inp + inbuffer.size(); + + for (unsigned int c = 0; c < nchannels; c++) + { + float* o = &(outbuffers[c].front()); + + for (const float* p = inp + c; p < endp; p += nchannels, o++) + { + *o = *p; + } + } +} + +void +interlace(const vector& inbuffers, SampleVector& outbuffer) +{ + const unsigned int nc = inbuffers.size(); + outbuffer.resize(inbuffers.front().size() * inbuffers.size()); + + // + // Have a special case for the most used forms + // + + if (inbuffers.size() == 1) + { + memcpy(&outbuffer.front(), + &inbuffers[0].front(), + inbuffers[0].size() * sizeof(float)); + } + else if (inbuffers.size() == 2) + { + const float* p0 = &inbuffers[0].front(); + const float* p1 = &inbuffers[1].front(); + float* o = &outbuffer.front(); + + for (const float* e = o + outbuffer.size(); o < e; o++, p0++, p1++) + { + *o = *p0; o++; + *o = *p1; + } + } + else + { + for (unsigned int i = 0; i < outbuffer.size(); i+= nc) + { + for (unsigned int q = 0; q < nc; q++) + { + outbuffer[i + q] = inbuffers[q][i/nc]; + } + } + } +} + +void +interlace(const vector& inbuffers, float* outbuffer, + size_t start, size_t num) +{ + const unsigned int nc = inbuffers.size(); + const size_t n = num > 0 ? num : inbuffers.front().size(); + + // + // Have a special case for the most used forms + // + + if (inbuffers.size() == 1) + { + memcpy(outbuffer + start, + &inbuffers[0].front(), + n * sizeof(float)); + } + else if (inbuffers.size() == 2) + { + assert(inbuffers[0].size() == inbuffers[1].size()); + assert(inbuffers[0].size() >= n); + assert(inbuffers[0].size() > 0); + assert(inbuffers[1].size() > 0); + const float* p0 = &(inbuffers[0].front()); + const float* p1 = &(inbuffers[1].front()); + float* o = outbuffer + (start * 2); + + for (const float* e = o + (n*2); + o < e; + o++, p0++, p1++) + { + *o = *p0; o++; + *o = *p1; + } + } + else + { + for (unsigned int i = 0; i < (n * nc); i+= nc) + { + for (unsigned int q = 0; q < nc; q++) + { + outbuffer[i + q + start * nc] = inbuffers[q][i/nc]; + } + } + } +} + + +} // TwkAudio diff --git a/src/lib/audio/TwkAudio/Mix.cpp b/src/lib/audio/TwkAudio/Mix.cpp new file mode 100644 index 000000000..4ca1761f6 --- /dev/null +++ b/src/lib/audio/TwkAudio/Mix.cpp @@ -0,0 +1,130 @@ +//****************************************************************************** +// Copyright (c) 2008 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** + +#include +#include +#include +#include +#include + +namespace TwkAudio { +using namespace TwkMath; +using namespace std; + +void +mixChannels(const AudioBuffer& in, + AudioBuffer& out, + const float lVolume, + const float rVolume, + const bool compose) +{ + const size_t inSize = in.size(); + const size_t outSize = out.size(); + + if (inSize != outSize) + { + cout << "WARNING: audio mix: inSize = " << inSize + << ", outSize = " << outSize << endl; + return; + } + + // XXX Uncomment to skip mix + // cerr << "MIX inChans: " << in.numChannels() << " inSize: " << in.size() << endl; + // memcpy(out.pointer(), in.pointer(), in.numChannels() * in.size() * sizeof(float)); + // return; + + const float* ip = in.pointer(); + float* op = out.pointer(); + + if ((in.channels() == out.channels()) && (lVolume == rVolume)) + { + size_t sampleCount = inSize * in.numChannels(); + if (compose) + { + for (size_t s = 0; s < sampleCount; s++) + { + (*op++) += (*ip++) * lVolume; + } + } + else + { + for (size_t s = 0; s < sampleCount; s++) + { + (*op++) = (*ip++) * lVolume; + } + } + } + else + { + ChannelsMap chmap; + initChannelsMap(in.channels(), out.channels(), chmap); + size_t sampleCount = inSize; + if (compose) + { + for (size_t s = 0; s < sampleCount; s++) + { + for (int och = 0; och < out.channels().size(); och++) + { + const ChannelMixState &cmixState = chmap[out.channels()[och]]; + + float lMix = 0.0f; + const std::vector &leftChs = cmixState.lefts; + for (int n = 0; n < leftChs.size(); ++n) + { + const ChannelState &chState = leftChs[n]; + lMix += chState.weight * ip[chState.index]; + } + + float rMix = 0.0f; + const std::vector &rightCh = cmixState.rights; + for (int n=0; n < rightCh.size(); ++n) + { + const ChannelState &chState = rightCh[n]; + rMix += chState.weight * ip[chState.index]; + } + + (*op++) += lMix * lVolume + rMix * rVolume; + } + + ip += in.numChannels(); + } + } + else + { + for (size_t s = 0; s < sampleCount; s++) + { + for (int och = 0; och < out.channels().size(); och++) + { + const ChannelMixState &cmixState = chmap[out.channels()[och]]; + + float lMix = 0.0f; + const std::vector &leftChs = cmixState.lefts; + for (int n = 0; n < leftChs.size(); ++n) + { + const ChannelState &chState = leftChs[n]; + lMix += chState.weight * ip[chState.index]; + } + + float rMix = 0.0f; + const std::vector &rightCh = cmixState.rights; + for (int n=0; n < rightCh.size(); ++n) + { + const ChannelState &chState = rightCh[n]; + rMix += chState.weight * ip[chState.index]; + } + + (*op++) = lMix * lVolume + rMix * rVolume; + } + + ip += in.numChannels(); + } + } + } +} + +} // TwkAudio diff --git a/src/lib/audio/TwkAudio/Resampler.cpp b/src/lib/audio/TwkAudio/Resampler.cpp new file mode 100644 index 000000000..9c6d215c1 --- /dev/null +++ b/src/lib/audio/TwkAudio/Resampler.cpp @@ -0,0 +1,205 @@ +//****************************************************************************** +// Copyright (c) 2007 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#include +#include +#include +#include +#include +#include +#include +#include + +namespace TwkAudio { +using namespace std; + +Resampler::Resampler(double factor, size_t blocksize) + : m_factor(factor), + m_handle(0), + m_filterwidth(0), + m_blocksize(blocksize) +{ + open(); +} + +Resampler::~Resampler() +{ + close(); +} + +size_t +Resampler::process(const float* in, + size_t inSize, + float* out, + size_t outSize, + bool endFlag, + bool enableClamp) +{ + int inUsed = 0; + int inUsedTotal = 0; + int outUsedTotal = 0; + + for (int i=0; true; i++) + { + size_t left = inSize - inUsedTotal; + int bsize = std::min(left, m_blocksize); + + size_t outUsed = resample_process(m_handle, + m_factor, + (float*)(in + inUsedTotal), + bsize, + endFlag && left < m_blocksize, + &inUsed, + out + outUsedTotal, + outSize - outUsedTotal); + + inUsedTotal += inUsed; + + if (outUsed > 0) + { + outUsedTotal += outUsed; + } + else if (outUsed <= 0 || (outUsed == 0 && inUsedTotal >= inSize)) + { + break; + } + } + + if (enableClamp) + { + float* data = out; + for (int i=0; i < outUsedTotal; ++i, data++) + { + if ((*data) > 1.0f) (*data) = 1.0f; + else if ((*data) < -1.0f) (*data) = -1.0f; + } + } + + return outUsedTotal; +} + +void +Resampler::process(const SampleVector& in, SampleVector& out, bool endFlag, bool enableClamp) +{ + size_t n = size_t(double(in.size()) * m_factor); + out.resize(n); + process(&in.front(), in.size(), &out.front(), out.size(), endFlag, enableClamp); +} + + +void +Resampler::reset() +{ + resample_reset(m_handle); +} + +void +Resampler::open() +{ + m_handle = resample_open(1, m_factor, m_factor); + m_filterwidth = resample_get_filter_width(m_handle); +} + +void +Resampler::close() +{ + if (m_handle) + { + resample_close(m_handle); + } + + m_handle = 0; + m_filterwidth = 0; +} + +//---------------------------------------------------------------------- + +MultiResampler::MultiResampler(int numChannels, + double factor, + size_t blocksize) + : m_inbuffers(numChannels), + m_outbuffers(numChannels) +{ + for (int ch = 0; ch < numChannels; ch++) + { + m_resamplers.push_back(new Resampler(factor, blocksize)); + } +} + +MultiResampler::~MultiResampler() +{ + for (int ch = 0; ch < m_resamplers.size(); ch++) + { + delete m_resamplers[ch]; + m_resamplers[ch] = 0; + } + m_resamplers.clear(); +} + +void +MultiResampler::close() +{ + for (int ch = 0; ch < m_resamplers.size(); ch++) + { + m_resamplers[ch]->close(); + } +} + +size_t +MultiResampler::process(const float* in, + size_t inSize, + float* out, + size_t outSize, + bool endFlag, + bool enableClamp) +{ + deinterlace(in, inSize, m_resamplers.size(), m_inbuffers); + + size_t n = size_t(double(inSize) * factor() + 0.49); + size_t bsize = max(n, m_outbuffers.front().size()); + + if (bsize != m_outbuffers.front().size()) + { + for (int ch = 0; ch < m_resamplers.size(); ch++) + { + m_outbuffers[ch].resize(bsize); + } + } + + size_t samples = 0; + for (int ch = 0; ch < m_resamplers.size(); ch++) + { + size_t nsamps = m_resamplers[ch]->process(&(m_inbuffers[ch].front()), + inSize, + &(m_outbuffers[ch].front()), + outSize, + endFlag, + enableClamp); +#ifndef NDEBUG + if (samples != 0 && samples != nsamps) + { + cout << "WARNING: resampler samples missmatch" << endl; + } +#endif + samples = nsamps; + } + + interlace(m_outbuffers, out, 0, min(n, outSize)); + + return samples; +} + +void +MultiResampler::reset() +{ + for (int ch = 0; ch < m_resamplers.size(); ch++) + { + m_resamplers[ch]->reset(); + } +} + +} // TwkAudio diff --git a/src/lib/audio/TwkAudio/ScaleTime.cpp b/src/lib/audio/TwkAudio/ScaleTime.cpp new file mode 100644 index 000000000..7f4b87885 --- /dev/null +++ b/src/lib/audio/TwkAudio/ScaleTime.cpp @@ -0,0 +1,63 @@ +// +// Copyright (c) 2008 Tweak Software. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +// +#include +#include +#include + +namespace TwkAudio { +using namespace std; +using namespace TwkMath; + + +void scaleTime(const AudioBuffer& inbuffer, AudioBuffer& outbuffer) +{ + const size_t insize = inbuffer.size(); + const size_t outsize = outbuffer.size(); + const size_t ch = outbuffer.numChannels(); + + if (insize == outsize) + { + memcpy(outbuffer.pointer(), + inbuffer.pointer(), + inbuffer.sizeInBytes()); + } + else if (insize > outsize) + { + if (ch == 1) + { + + size_t index = 0; + double s = outbuffer.size(); + float* o = outbuffer.pointer(); + + for (const float* i = inbuffer.pointer(), *e = i + outbuffer.size(); + i < e; + i++, index++) + { + *o = *i * smoothstep(1.0 - double(index) / s); + } + + index = 0; + o = outbuffer.pointer(); + + for (const float* i = inbuffer.pointer() + (insize - outsize), + *e = i + outbuffer.size(); + i < e; + i++, index++) + { + *o = *i * smoothstep(double(index) / s); + } + } + else if (ch == 2) + { + } + } +} + + +} // TwkAudio diff --git a/src/lib/audio/TwkAudio/TwkAudio/Audio.h b/src/lib/audio/TwkAudio/TwkAudio/Audio.h new file mode 100644 index 000000000..fa9d5c69a --- /dev/null +++ b/src/lib/audio/TwkAudio/TwkAudio/Audio.h @@ -0,0 +1,372 @@ +//****************************************************************************** +// Copyright (c) 2007 Tweak Inc. +// All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 +// +//****************************************************************************** +#ifndef __TwkAudio__Audio__h__ +#define __TwkAudio__Audio__h__ + +#include +#include +#include +#include +#include +#include + +#ifdef _MSC_VER +#ifdef max +#undef max +#endif +#endif + + +#define TWEAK_AUDIO_DEFAULT_SAMPLE_RATE 48000 + +//#define ENABLE_AUDIOBUFFER_CHECK + +#ifdef ENABLE_AUDIOBUFFER_CHECK +#define AUDIOBUFFER_CHECK(_audioBufferInst, _label) { (_audioBufferInst).checkBuffer(_label); } +#else +#define AUDIOBUFFER_CHECK(_audioBufferInst, _label) +#endif + +namespace TwkAudio { + +typedef double Time; +static const Time Always = std::numeric_limits