diff --git a/.github/workflows/ci_workflow.yml b/.github/workflows/ci_workflow.yml index 3722ec3382..07db553ea4 100644 --- a/.github/workflows/ci_workflow.yml +++ b/.github/workflows/ci_workflow.yml @@ -61,7 +61,7 @@ jobs: strategy: fail-fast: true matrix: - build: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + build: [7, 8, 9, 10, 11, 12] include: # ------------------------------------------------------------------- # VFX CY2024 (Python 3.11) @@ -147,6 +147,118 @@ jobs: compiler-desc: GCC vfx-cy: 2023 install-ext-packages: ALL + env: + CXX: ${{ matrix.cxx-compiler }} + CC: ${{ matrix.cc-compiler }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install docs env + run: share/ci/scripts/linux/yum/install_docs_env.sh + if: matrix.build-docs == 'ON' + - name: Install tests env + run: share/ci/scripts/linux/yum/install_tests_env.sh + - name: Create build directories + run: | + mkdir _install + mkdir _build + - name: Configure + run: | + cmake ../. \ + -DCMAKE_INSTALL_PREFIX=../_install \ + -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ + -DCMAKE_CXX_STANDARD=${{ matrix.cxx-standard }} \ + -DBUILD_SHARED_LIBS=${{ matrix.build-shared }} \ + -DOCIO_BUILD_DOCS=${{ matrix.build-docs }} \ + -DOCIO_BUILD_OPENFX=${{ matrix.build-openfx }} \ + -DOCIO_BUILD_GPU_TESTS=OFF \ + -DOCIO_USE_SIMD=${{ matrix.use-simd }} \ + -DOCIO_USE_OIIO_FOR_APPS=${{ matrix.use-oiio }} \ + -DOCIO_INSTALL_EXT_PACKAGES=${{ matrix.install-ext-packages }} \ + -DOCIO_WARNING_AS_ERROR=ON \ + -DPython_EXECUTABLE=$(which python) + working-directory: _build + - name: Build + run: | + cmake --build . \ + --target install \ + --config ${{ matrix.build-type }} \ + -- -j$(nproc) + echo "ocio_build_path=$(pwd)" >> $GITHUB_ENV + working-directory: _build + - name: Test + run: ctest -V -C ${{ matrix.build-type }} + working-directory: _build + - name: Test CMake Consumer with shared OCIO + if: matrix.build-shared == 'ON' + run: | + cmake . \ + -DCMAKE_PREFIX_PATH=../../../_install \ + -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} + cmake --build . \ + --config ${{ matrix.build-type }} + ./consumer + working-directory: _build/tests/cmake-consumer-dist + - name: Test CMake Consumer with static OCIO + if: matrix.build-shared == 'OFF' + # The yaml-cpp_VERSION is set below because Findyaml-cpp.cmake needs it but is unable to + # extract it from the headers, like the other modules. + # + # Prefer the static version of each dependencies by using _STATIC_LIBRARY. + # Alternatively, this can be done by setting _LIBRARY and _INCLUDE_DIR to + # the static version of the package. + run: | + cmake . \ + -DCMAKE_PREFIX_PATH=../../../_install \ + -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ + -Dexpat_ROOT=${{ env.ocio_build_path }}/ext/dist \ + -Dexpat_STATIC_LIBRARY=ON \ + -DImath_ROOT=${{ env.ocio_build_path }}/ext/dist \ + -DImath_STATIC_LIBRARY=ON \ + -Dpystring_ROOT=${{ env.ocio_build_path }}/ext/dist \ + -Dyaml-cpp_ROOT=${{ env.ocio_build_path }}/ext/dist \ + -Dyaml-cpp_STATIC_LIBRARY=ON \ + -Dyaml-cpp_VERSION=0.7.0 \ + -DZLIB_ROOT=${{ env.ocio_build_path }}/ext/dist \ + -DZLIB_STATIC_LIBRARY=ON \ + -Dminizip-ng_ROOT=${{ env.ocio_build_path }}/ext/dist \ + -Dminizip-ng_STATIC_LIBRARY=ON + cmake --build . \ + --config ${{ matrix.build-type }} + ./consumer + working-directory: _build/tests/cmake-consumer-dist + + # --------------------------------------------------------------------------- + # Linux (unsupported Node.js) + # --------------------------------------------------------------------------- + + linux-old: + name: 'Linux VFX CY${{ matrix.vfx-cy }} + <${{ matrix.compiler-desc }} + config=${{ matrix.build-type }}, + shared=${{ matrix.build-shared }}, + simd=${{ matrix.use-simd }}, + cxx=${{ matrix.cxx-standard }}, + docs=${{ matrix.build-docs }}, + oiio=${{ matrix.use-oiio }}>' + # Avoid duplicated checks when a pull_request is opened from a local branch. + if: | + github.event_name == 'push' || + github.event.pull_request.head.repo.full_name != github.repository + # GH-hosted VM. The build runs in ASWF 'container' defined below. + runs-on: ubuntu-latest + container: + # DockerHub: https://hub.docker.com/u/aswf + # Source: https://github.com/AcademySoftwareFoundation/aswf-docker + image: aswf/ci-ocio:${{ matrix.vfx-cy }} + volumes: + - /node20217:/node20217:rw,rshared + - /node20217:/__e/node20:ro,rshared + strategy: + fail-fast: true + matrix: + build: [1, 2, 3, 4, 5, 6] + include: # ------------------------------------------------------------------- # VFX CY2022 (Python 3.9) # ------------------------------------------------------------------- @@ -234,9 +346,23 @@ jobs: env: CXX: ${{ matrix.cxx-compiler }} CC: ${{ matrix.cc-compiler }} - ACTIONS_RUNNER_FORCE_ACTIONS_NODE_VERSION: node16 - ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true steps: + # Install nodejs 20 with glibc 2.17, to work around the face that the + # GHA runners are insisting on a node version that is too new for the + # glibc in the ASWF containers prior to 2023. + - name: Install nodejs20glibc2.17 + run: | + curl --silent https://unofficial-builds.nodejs.org/download/release/v20.18.1/node-v20.18.1-linux-x64-glibc-217.tar.xz | tar -xJ --strip-components 1 -C /node20217 -f - + # We would like to use harden-runner, but it flags too many false + # positives, every time we download a dependency. We should use it only + # on CI runs where we are producing artifacts that users might rely on. + # - name: Harden Runner + # uses: step-security/harden-runner@248ae51c2e8cc9622ecf50685c8bf7150c6e8813 # v1.4.3 + # with: + # egress-policy: audit # TODO: change to 'egress-policy: block' after couple of runs + - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0 + # Note: can't upgrade to actions/checkout 4.0 because it needs newer + # glibc than these containers have. - name: Checkout uses: actions/checkout@v3 - name: Install docs env @@ -319,7 +445,7 @@ jobs: # --------------------------------------------------------------------------- macos: - name: 'macOS 12 + name: 'macOS 13 directory +The ``install_docs_env.sh`` script in the ``share/ci/scripts/`` directory will install the Python-related requirements for building the documentation (Sphinx, six, testresources, recommonmark, sphinx-press-theme, sphinx-tabs, and breathe) and Doxygen. +**Note:** If you are on Linux using yum and don't already have Doxygen installed, you will have to +uncomment the relevant line in ``share/ci/scripts/linux/yum/install_docs_env.sh`` + Use GitBash (`provided with Git for Windows `_) to execute the script on Windows. Python 3 is required to build the documentation. If you have multiple Python installs you'll need to make sure pip and CMake find the correct version. You can manually inform CMake of which to use by adding this option to the below -`cmake` command, which configures the documentation build: +`cmake` command, which configures the documentation build:: -DPython_ROOT= -For the Python packages, ensure their locations are in your ``PYTHONPATH`` -environment variable prior to configuring the build. +You need to make sure these Python package locations are in your ``PYTHONPATH`` +environment variable prior to configuring the build. A good way to accomplish this is +with a virtual environment, which would look like:: + + $ python3 -m venv venv + $ source venv/bin/activate + $ share/ci/scripts//install_docs_env.sh # macos or windows + or + $ share/ci/scripts/linux/install_docs_env.sh # for linux, tool is apt or yum + $ export PYTHONPATH=/venv/lib/python/site-packages + +Obviously, adjust for specific paths and python versions. Also, the above assumes a ``bash`` +environment - the commands may be slightly different for other shells. Building the docs ***************** @@ -58,25 +72,32 @@ Initial run:: Then after each change you wish to preview:: - $ cmake -D OCIO_BUILD_DOCS=ON .. && make docs + $ cmake -D OCIO_BUILD_DOCS=ON ../ && make docs + +Tip: + The ``-j`` option to ``make`` is your friend, eg, ``make -j 8`` will make things go much faster. + The exact number will depend on the resources of your particular machine. The ``nproc`` command (linux) will help you decide. Updating the Python docs ************************ If a contributor makes changes to any part of OCIO which affects the Python API docs (so, public headers, Python bindings, any documentation process code, etc.) they should -do a local build with the new CMake option -DOCIO_BUILD_FROZEN_DOCS=ON, and add the -modified rST files under docs/api/python/frozen to their PR. +do a local build with the new CMake option -DOCIO_BUILD_FROZEN_DOCS=ON:: + + $ cmake -DOCIO_BUILD_FROZEN_DOCS=ON ../ + +and add the modified rST files found under ``docs/api/python/frozen`` to their PR. -Note: If you run the scripts on Linux, the freezing process should work well. On other +**Note:** If you run the scripts on Linux, the freezing process should work well. On other platforms, the process may sometimes make spurious deltas to rST files unrelated to your changes. Please don't add these files to your PR. The OCIO conf.py module has a switch that detects when docs are being built on GH Actions -(CI env var == true) it will backup the frozen folder to a sibling backup folder on Sphinx -init, and following Sphinx build completion will do a file-by-file comparison of the new -frozen and the backup folders. If there are differences, the CI job may fail with an error -explaining where the differences were found and with instructions on how to fix them. +(CI env var == true); in that case it will backup the "frozen" folder to a sibling backup +folder on Sphinx init, and following Sphinx build completion will do a file-by-file comparison +of the new "frozen" and backup folders. If there are differences, the CI job may fail with +an error explaining where the differences were found and with instructions on how to fix them. The conf.py also has a switch that detects when it is being run on RTD, and in that case will itself run Doxygen to generate the XML needed by breathe prior to building the docs, @@ -88,14 +109,14 @@ nothing more. Right now that only works when the READTHEDOCS env var == True, bu be easily exposed another way if needed. These features required several custom Sphinx extensions tuned for our project which are -located under share/docs. +located under ``share/docs``. Building the docs -- Excluding the API docs ******************************************* If you don't need to build the API documentation, there is a quick and dirty way to do a docs build. This approach does not need to compile the C++ code but is not ideal -since it modifies files in the source directory rather than the build directory: +since it modifies files in the source directory rather than the build directory:: export READTHEDOCS=True cd docs (in the source directory) diff --git a/docs/guides/contributing/doxygen_style_guide.rst b/docs/guides/contributing/doxygen_style_guide.rst index a4973647a7..083f1614f3 100644 --- a/docs/guides/contributing/doxygen_style_guide.rst +++ b/docs/guides/contributing/doxygen_style_guide.rst @@ -67,8 +67,8 @@ Where possible please try to split the tag and names from the descriptive text. * * Compresses an input string using the foobar algorithm. * - * \param - * Uncompressed The input string. + * \param uncompressed + * The input string. * \return * A compressed version of the input string. */ diff --git a/docs/guides/using_ocio/compatible_software.rst b/docs/guides/using_ocio/compatible_software.rst index 0fbfac062c..54801c6462 100644 --- a/docs/guides/using_ocio/compatible_software.rst +++ b/docs/guides/using_ocio/compatible_software.rst @@ -131,6 +131,23 @@ CryEngine is a real-time game engine, targeting applications in the motion-pictu Website: ``__ +Disguise +******** + +Disguise is an integrated hardware and software platform powering the world's biggest live events, immersive experiences and virtual production stages. OpenColorIO is integrated into the Designer software, allowing users to manage the color spaces of all content sources and physical output devices. + +Website: ``__ + +Supported version: >= 29.1 + +Documentation: + + +- `OpenColorIO Support `__ + +- `Color Management `__ + + DJV *** @@ -336,17 +353,13 @@ PhotoFlow supports OCIO via a dedicated tool that can load a given configuration Website : ``__ -Photoshop (beta) -**************** - -OCIO can be enabled via a technology preview checkbox in preferences. For more details see `OpenColorIO and 32-bit Editing now available in Photoshop Beta `__. - Photoshop ********* -OpenColorIO display luts can be exported as ICC profiles for use in photoshop. The core idea is to create an .icc profile, with a valid description, and then to save it to the proper OS icc directory. (On OSX, ``~/Library/ColorSync/Profiles/``). Upon a Photoshop relaunch, Edit->Assign Profile, and then select your new OCIO lut. +OCIO can be enabled via OpenColorIO settings. For more details see `OpenColorIO and tools with 32-bit mode `__. -Website : ``__ +Photoshop Fnordware plugin +************************** An OpenColorIO plugin is also available for use in Photoshop. The plug-in can perform color operations to an image as a filter and can also export LUTs and ICC profiles to be used by Photoshop. diff --git a/docs/quick_start/installation.rst b/docs/quick_start/installation.rst index 5c34c9fe99..8b8e275d53 100644 --- a/docs/quick_start/installation.rst +++ b/docs/quick_start/installation.rst @@ -126,7 +126,7 @@ Required components: - CMake >= 3.14 - \*Expat >= 2.4.1 (XML parser for CDL/CLF/CTF) - \*yaml-cpp >= 0.7.0 (YAML parser for Configs) -- \*Imath >= 3.0 (for half domain LUTs) +- \*Imath >= 3.1.1 (for half domain LUTs) - \*pystring >= 1.1.3 - \*minizip-ng >= 3.0.7 (for config archiving) - \*ZLIB >= 1.2.13 (for config archiving) @@ -135,14 +135,14 @@ Optional OCIO functionality also depends on: - \*Little CMS >= 2.2 (for ociobakelut ICC profile baking) - \*OpenGL GLUT & GLEW (for ociodisplay) -- \*OpenEXR >= 3.0 (for apps including ocioconvert) -- OpenImageIO >= 2.1.9 (for apps including ocioconvert) +- \*OpenEXR >= 3.0.5 (for apps including ocioconvert) +- OpenImageIO >= 2.2.14 (for apps including ocioconvert) - \*OpenFX >= 1.4 (for the OpenFX plug-ins) - OpenShadingLanguage >= 1.11 (for the OSL unit tests) - Doxygen (for the docs) - NumPy (optionally used in the Python test suite) - \*pybind11 >= 2.9.2 (for the Python binding) -- Python >= 2.7 (for the Python binding only) +- Python >= 3.7 (for the Python binding only) - Python 3.7 - 3.9 (for building the documentation) Building the documentation requires the following packages, available via PyPI: diff --git a/docs/site/homepage/data/en/supported_apps.yml b/docs/site/homepage/data/en/supported_apps.yml index 1854374772..e5387b008d 100644 --- a/docs/site/homepage/data/en/supported_apps.yml +++ b/docs/site/homepage/data/en/supported_apps.yml @@ -58,6 +58,13 @@ supported_apps: categories : ["Game Engine", "3D"] content : Version 3 and up. link : "https://www.cryengine.com/" + + # portfolio item loop + - name : Disguise + image : images/supported_apps/disguise.png + categories : ["2D", "3D", "Compositing", "Playback", "Simulation"] + content : Designer r29.1 and up. + link : "https://disguise.one/" # portfolio item loop - name : DJV @@ -161,8 +168,8 @@ supported_apps: - name : Adobe Photoshop image : images/supported_apps/ps.png categories : ["2D", "Photography", "Paint"] - content : Beta support. - link : "https://community.adobe.com/t5/photoshop-beta-discussions/new-feature-opencolorio-and-32-bit-editing-now-available-in-photoshop-beta/m-p/14787280" + content : Native support. + link : "https://helpx.adobe.com/photoshop/using/opencolorio-transform.html" # portfolio item loop - name : PhotoFlow diff --git a/docs/site/homepage/static/images/supported_apps/disguise.png b/docs/site/homepage/static/images/supported_apps/disguise.png new file mode 100644 index 0000000000..b6777fa49f Binary files /dev/null and b/docs/site/homepage/static/images/supported_apps/disguise.png differ diff --git a/docs/toc_redirect.rst b/docs/toc_redirect.rst index 2dd5537956..13ffcfc8ab 100644 --- a/docs/toc_redirect.rst +++ b/docs/toc_redirect.rst @@ -40,4 +40,4 @@ .. toctree:: :hidden: - aswf/_index \ No newline at end of file + aswf/_index diff --git a/include/OpenColorIO/OpenColorTypes.h b/include/OpenColorIO/OpenColorTypes.h index 33e02d6d18..21243eb363 100644 --- a/include/OpenColorIO/OpenColorTypes.h +++ b/include/OpenColorIO/OpenColorTypes.h @@ -449,11 +449,14 @@ enum GpuLanguage GPU_LANGUAGE_GLSL_1_2, ///< OpenGL Shading Language GPU_LANGUAGE_GLSL_1_3, ///< OpenGL Shading Language GPU_LANGUAGE_GLSL_4_0, ///< OpenGL Shading Language - GPU_LANGUAGE_HLSL_DX11, ///< DirectX Shading Language + GPU_LANGUAGE_HLSL_SM_5_0, ///< DirectX High Level Shading Language LANGUAGE_OSL_1, ///< Open Shading Language GPU_LANGUAGE_GLSL_ES_1_0, ///< OpenGL ES Shading Language GPU_LANGUAGE_GLSL_ES_3_0, ///< OpenGL ES Shading Language - GPU_LANGUAGE_MSL_2_0 ///< Metal Shading Language + GPU_LANGUAGE_MSL_2_0, ///< Metal Shading Language + + // Deprecated enum(s) + GPU_LANGUAGE_HLSL_DX11 = GPU_LANGUAGE_HLSL_SM_5_0 }; /// Controls which environment variables are loaded into a Context object. diff --git a/share/cmake/utils/CompilerFlags.cmake b/share/cmake/utils/CompilerFlags.cmake index 91e438f455..536b5eebd5 100644 --- a/share/cmake/utils/CompilerFlags.cmake +++ b/share/cmake/utils/CompilerFlags.cmake @@ -62,6 +62,9 @@ if(USE_MSVC) ) endif() + # Make MSVC compiler report correct __cplusplus version (otherwise reports 199711L) + set(PLATFORM_COMPILE_OPTIONS "${PLATFORM_COMPILE_OPTIONS};/Zc:__cplusplus") + # Explicitely specify the default warning level i.e. /W3. # Note: Do not use /Wall (i.e. /W4) which adds 'informational' warnings. set(PLATFORM_COMPILE_OPTIONS "${PLATFORM_COMPILE_OPTIONS};/W3") @@ -74,6 +77,9 @@ if(USE_MSVC) set(PLATFORM_COMPILE_OPTIONS "${PLATFORM_COMPILE_OPTIONS};/WX") endif() + # Enable parallel compilation of source files + set(PLATFORM_COMPILE_OPTIONS "${PLATFORM_COMPILE_OPTIONS};/MP") + elseif(USE_CLANG) set(PLATFORM_COMPILE_OPTIONS "${PLATFORM_COMPILE_OPTIONS};-DUSE_CLANG") diff --git a/share/nuke/examples/colorlookup_to_spi1d.py b/share/nuke/examples/colorlookup_to_spi1d.py index c5e9814ee9..241dd24a54 100644 --- a/share/nuke/examples/colorlookup_to_spi1d.py +++ b/share/nuke/examples/colorlookup_to_spi1d.py @@ -19,7 +19,7 @@ def WriteSPI1D(filename, fromMin, fromMax, data): SIZE = 2**10 data = [] -for i in xrange(SIZE): +for i in range(SIZE): x = i/(SIZE-1.0) data.append(knob.getValueAt(x)) diff --git a/share/nuke/examples/ocio_to_colorlookup_all.py b/share/nuke/examples/ocio_to_colorlookup_all.py index 83d15c7430..69f19bd58f 100644 --- a/share/nuke/examples/ocio_to_colorlookup_all.py +++ b/share/nuke/examples/ocio_to_colorlookup_all.py @@ -27,7 +27,7 @@ def Fit(value, fromMin, fromMax, toMin, toMax): return (value - fromMin) / (fromMax - fromMin) * (toMax - toMin) + toMin SIZE = 2**10 -for i in xrange(SIZE): +for i in range(SIZE): x = i/(SIZE-1.0) x = Fit(x, 0.0, 1.0, -0.125, 1.5) diff --git a/share/nuke/examples/ocio_to_colorlookup_rgb.py b/share/nuke/examples/ocio_to_colorlookup_rgb.py index af847d6b4f..e5016d8e08 100644 --- a/share/nuke/examples/ocio_to_colorlookup_rgb.py +++ b/share/nuke/examples/ocio_to_colorlookup_rgb.py @@ -21,7 +21,7 @@ SIZE = 11 -for i in xrange(SIZE): +for i in range(SIZE): x = i/(SIZE-1.0) y = processor.applyRGB((x,x,x)) diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index 3bde3abb98..a4cf7c5fe4 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -5268,7 +5268,9 @@ void Config::Impl::checkVersionConsistency(ConstTransformRcPtr & transform) cons || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-REC2020-D60-in-REC2020-D65_2.0") || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-REC2020-D60-in-REC2020-D65_2.0") || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-REC2020-D60-in-REC2020-D65_2.0") - || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020-D60-in-REC2020-D65_2.0") ) + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020-D60-in-REC2020-D65_2.0") + // NB: This one was added in OCIO 2.4.1. + || 0 == Platform::Strcasecmp(blt->getStyle(), "DISPLAY - CIE-XYZ-D65_to_DisplayP3-HDR") ) ) { std::ostringstream os; diff --git a/src/OpenColorIO/GpuShaderClassWrapper.cpp b/src/OpenColorIO/GpuShaderClassWrapper.cpp index 35dce54add..e0c075a11d 100644 --- a/src/OpenColorIO/GpuShaderClassWrapper.cpp +++ b/src/OpenColorIO/GpuShaderClassWrapper.cpp @@ -31,7 +31,7 @@ std::unique_ptr GpuShaderClassWrapper::CreateClassWrapper case GPU_LANGUAGE_GLSL_1_2: case GPU_LANGUAGE_GLSL_1_3: case GPU_LANGUAGE_GLSL_4_0: - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: case GPU_LANGUAGE_GLSL_ES_1_0: case GPU_LANGUAGE_GLSL_ES_3_0: default: diff --git a/src/OpenColorIO/GpuShaderUtils.cpp b/src/OpenColorIO/GpuShaderUtils.cpp index d439c9f859..03f90c579f 100644 --- a/src/OpenColorIO/GpuShaderUtils.cpp +++ b/src/OpenColorIO/GpuShaderUtils.cpp @@ -56,7 +56,7 @@ std::string getVecKeyword(GpuLanguage lang) } case GPU_LANGUAGE_MSL_2_0: - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: { kw << "float" << N; break; @@ -98,7 +98,7 @@ void getTexDecl(GpuLanguage lang, samplerDecl = kw.str(); break; } - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: { std::ostringstream t; t << "Texture" << N << "D " << textureName << ";"; @@ -166,7 +166,7 @@ std::string getTexSample(GpuLanguage lang, kw << "tex" << N << "D(" << samplerName << ", " << coords << ")"; break; } - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: { kw << textureName << ".Sample(" << samplerName << ", " << coords << ")"; break; @@ -345,7 +345,7 @@ std::string GpuShaderText::constKeyword() const str += " "; break; } - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: { str += "static const"; str += " "; @@ -537,7 +537,7 @@ void GpuShaderText::declareFloatArrayConst(const std::string & name, int size, c } case LANGUAGE_OSL_1: case GPU_LANGUAGE_CG: - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: case GPU_LANGUAGE_MSL_2_0: { nl << floatKeywordConst() << " " << name << "[" << size << "] = {"; @@ -589,7 +589,7 @@ void GpuShaderText::declareIntArrayConst(const std::string & name, int size, con nl << ");"; break; } - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: case GPU_LANGUAGE_MSL_2_0: { nl << intKeywordConst() << " " << name << "[" << size << "] = {"; @@ -940,7 +940,7 @@ std::string matrix3Mul(const T * m3x3, const std::string & vecName, GpuLanguage << getMatrixValues(m3x3, lang, false) << "), " << vecName << ")"; break; } - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: { kw << "mul(" << vecName << ", float3x3(" << getMatrixValues(m3x3, lang, true) << "))"; @@ -1006,7 +1006,7 @@ std::string matrix4Mul(const T * m4x4, const std::string & vecName, GpuLanguage << getMatrixValues(m4x4, lang, false) << "), " << vecName << ")"; break; } - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: { kw << "mul(" << vecName << ", float4x4(" << getMatrixValues(m4x4, lang, true) << "))"; @@ -1062,7 +1062,7 @@ std::string GpuShaderText::lerp(const std::string & x, break; } case GPU_LANGUAGE_CG: - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: { kw << "lerp(" << x << ", " << y << ", " << a << ")"; break; @@ -1094,7 +1094,7 @@ std::string GpuShaderText::float3GreaterThan(const std::string & a, } case LANGUAGE_OSL_1: case GPU_LANGUAGE_MSL_2_0: - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: { kw << float3Keyword() << "(" << "(" << a << "[0] > " << b << "[0]) ? 1.0 : 0.0, " @@ -1128,7 +1128,7 @@ std::string GpuShaderText::float4GreaterThan(const std::string & a, break; } case GPU_LANGUAGE_MSL_2_0: - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: { kw << float4Keyword() << "(" << "(" << a << "[0] > " << b << "[0]) ? 1.0 : 0.0, " @@ -1173,7 +1173,7 @@ std::string GpuShaderText::float3GreaterThanEqual(const std::string& a, } case LANGUAGE_OSL_1: case GPU_LANGUAGE_MSL_2_0: - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: { kw << float3Keyword() << "(" << "(" << a << "[0] >= " << b << "[0]) ? 1.0 : 0.0, " @@ -1207,7 +1207,7 @@ std::string GpuShaderText::float4GreaterThanEqual(const std::string& a, break; } case GPU_LANGUAGE_MSL_2_0: - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: { kw << float4Keyword() << "(" << "(" << a << "[0] >= " << b << "[0]) ? 1.0 : 0.0, " @@ -1251,7 +1251,7 @@ std::string GpuShaderText::atan2(const std::string & y, kw << "atan(" << y << ", " << x << ")"; break; } - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: { // note: Various internet sources claim that the x & y arguments need to be // swapped for HLSL (relative to GLSL). However, recent testing on Windows @@ -1285,7 +1285,7 @@ std::string GpuShaderText::sign(const std::string & v) const case GPU_LANGUAGE_GLSL_4_0: case GPU_LANGUAGE_GLSL_ES_1_0: case GPU_LANGUAGE_GLSL_ES_3_0: - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: case GPU_LANGUAGE_MSL_2_0: { kw << "sign(" << v << ");"; diff --git a/src/OpenColorIO/LookParse.cpp b/src/OpenColorIO/LookParse.cpp index 2b83270e0d..e70a09cf4b 100644 --- a/src/OpenColorIO/LookParse.cpp +++ b/src/OpenColorIO/LookParse.cpp @@ -17,13 +17,13 @@ void LookParseResult::Token::parse(const std::string & str) { // Assert no commas, colons, or | in str. - if(StringUtils::StartsWith(str, "+")) + if(StringUtils::StartsWith(str, '+')) { name = StringUtils::LeftTrim(str, '+'); dir = TRANSFORM_DIR_FORWARD; } // TODO: Handle -- - else if(StringUtils::StartsWith(str, "-")) + else if(StringUtils::StartsWith(str, '-')) { name = StringUtils::LeftTrim(str, '-'); dir = TRANSFORM_DIR_INVERSE; diff --git a/src/OpenColorIO/OpOptimizers.cpp b/src/OpenColorIO/OpOptimizers.cpp index 7788bfb164..58b395d5c6 100755 --- a/src/OpenColorIO/OpOptimizers.cpp +++ b/src/OpenColorIO/OpOptimizers.cpp @@ -68,7 +68,7 @@ bool IsCombineEnabled(OpData::Type type, OptimizationFlags flags) (type == OpData::RangeType && HasFlag(flags, OPTIMIZATION_COMP_RANGE)); } -constexpr int MAX_OPTIMIZATION_PASSES = 8; +constexpr int MAX_OPTIMIZATION_PASSES = 80; int RemoveNoOpTypes(OpRcPtrVec & opVec) { @@ -317,8 +317,10 @@ int CombineOps(OpRcPtrVec & opVec, OptimizationFlags oFlags) op1->combineWith(tmpops, op2); FinalizeOps(tmpops); - // tmpops may have any number of ops in it. (0, 1, 2, ...) - // (size 0 would occur only if the combination results in a no-op). + // The tmpops may have any number of ops in it: (0, 1, 2, ...). + // (Size 0 would occur only if the combination results in a no-op, + // for example, a pair of matrices that compose into a no-op are + // returned as empty rather than as an identity matrix.) // // No matter the number, we need to swap them in for the original ops. @@ -336,6 +338,15 @@ int CombineOps(OpRcPtrVec & opVec, OptimizationFlags oFlags) // We've done something so increment the count! ++count; + + // Break, since combining ops is less desirable than other optimization options. + // For example, it is preferable to remove a pair of ops using RemoveInverseOps + // rather than combining them. Consider this example: + // Lut1D A --> Matrix B --> Matrix C --> Lut1D Ainv + // If Matrix B & C are not pair inverses but do combine into an identity, then + // CombineOps would compose Lut1D A & Ainv, into a new Lut1D rather than + // allowing another round of optimization which would remove them as inverses. + break; } else { @@ -629,7 +640,7 @@ void OpRcPtrVec::optimize(OptimizationFlags oFlags) os << "**" << std::endl; os << "Optimized "; os << originalSize << "->" << finalSize << ", 1 pass, "; - os << total_nooptype << " noop types removed\n"; + os << total_nooptype << " no-op types removed\n"; os << SerializeOpVec(*this, 4); LogDebug(os.str()); } @@ -664,11 +675,21 @@ void OpRcPtrVec::optimize(OptimizationFlags oFlags) while (passes <= MAX_OPTIMIZATION_PASSES) { + // Remove all ops for which isNoOp is true, including identity matrices. int noops = optimizeIdentity ? RemoveNoOps(*this) : 0; + + // Replace all complex ops with simpler ops (e.g., a CDL which only scales with a matrix). // Note this might increase the number of ops. int replacedOps = replaceOps ? ReplaceOps(*this) : 0; + + // Replace all complex identities with simpler ops (e.g., an identity Lut1D with a range). int identityops = ReplaceIdentityOps(*this, oFlags); + + // Remove all adjacent pairs of ops that are inverses of each other. int inverseops = RemoveInverseOps(*this, oFlags); + + // Combine a pair of ops, for example multiply two adjacent Matrix ops. + // (Combines at most one pair on each iteration.) int combines = CombineOps(*this, oFlags); if (noops + identityops + inverseops + combines == 0) @@ -721,12 +742,12 @@ void OpRcPtrVec::optimize(OptimizationFlags oFlags) os << "Optimized "; os << originalSize << "->" << finalSize << ", "; os << passes << " passes, "; - os << total_nooptype << " noop types removed, "; - os << total_noops << " noops removed, "; + os << total_nooptype << " no-op types removed, "; + os << total_noops << " no-ops removed, "; os << total_replacedops << " ops replaced, "; os << total_identityops << " identity ops replaced, "; - os << total_inverseops << " inverse ops removed, "; - os << total_combines << " ops combines, "; + os << total_inverseops << " inverse op pairs removed, "; + os << total_combines << " ops combined, "; os << total_inverses << " ops inverted\n"; os << SerializeOpVec(*this, 4); LogDebug(os.str()); diff --git a/src/OpenColorIO/ParseUtils.cpp b/src/OpenColorIO/ParseUtils.cpp index 9ec635bebb..c8b77c5f3a 100644 --- a/src/OpenColorIO/ParseUtils.cpp +++ b/src/OpenColorIO/ParseUtils.cpp @@ -265,7 +265,7 @@ const char * GpuLanguageToString(GpuLanguage language) case GPU_LANGUAGE_GLSL_4_0: return "glsl_4.0"; case GPU_LANGUAGE_GLSL_ES_1_0: return "glsl_es_1.0"; case GPU_LANGUAGE_GLSL_ES_3_0: return "glsl_es_3.0"; - case GPU_LANGUAGE_HLSL_DX11: return "hlsl_dx11"; + case GPU_LANGUAGE_HLSL_SM_5_0: return "hlsl_sm_5.0"; case GPU_LANGUAGE_MSL_2_0: return "msl_2"; case LANGUAGE_OSL_1: return "osl_1"; } @@ -284,7 +284,7 @@ GpuLanguage GpuLanguageFromString(const char * s) else if(str == "glsl_4.0") return GPU_LANGUAGE_GLSL_4_0; else if(str == "glsl_es_1.0") return GPU_LANGUAGE_GLSL_ES_1_0; else if(str == "glsl_es_3.0") return GPU_LANGUAGE_GLSL_ES_3_0; - else if(str == "hlsl_dx11") return GPU_LANGUAGE_HLSL_DX11; + else if(str == "hlsl_sm_5.0") return GPU_LANGUAGE_HLSL_SM_5_0; else if(str == "osl_1") return LANGUAGE_OSL_1; else if(str == "msl_2") return GPU_LANGUAGE_MSL_2_0; @@ -672,13 +672,13 @@ bool nextline(std::istream &istream, std::string &line) { line.resize(line.size() - 1); } - if(!StringUtils::Trim(line).empty()) + if(!StringUtils::IsEmptyOrWhiteSpace(line)) { return true; } } - line = ""; + line.clear(); return false; } diff --git a/src/OpenColorIO/fileformats/FileFormat3DL.cpp b/src/OpenColorIO/fileformats/FileFormat3DL.cpp index 0babf55206..c4d90cc776 100755 --- a/src/OpenColorIO/fileformats/FileFormat3DL.cpp +++ b/src/OpenColorIO/fileformats/FileFormat3DL.cpp @@ -263,11 +263,11 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream, if(lineParts.empty()) continue; if (lineParts.size() > 0) { - if (StringUtils::StartsWith(lineParts[0], "#")) + if (StringUtils::StartsWith(lineParts[0], '#')) { continue; } - if (StringUtils::StartsWith(lineParts[0], "<")) + if (StringUtils::StartsWith(lineParts[0], '<')) { // Format error: reject files that could be // formatted as xml. diff --git a/src/OpenColorIO/fileformats/FileFormatIridasCube.cpp b/src/OpenColorIO/fileformats/FileFormatIridasCube.cpp index aa00f7febc..ca2965e373 100755 --- a/src/OpenColorIO/fileformats/FileFormatIridasCube.cpp +++ b/src/OpenColorIO/fileformats/FileFormatIridasCube.cpp @@ -180,7 +180,7 @@ LocalFileFormat::read(std::istream & istream, { ++lineNumber; // All lines starting with '#' are comments - if (StringUtils::StartsWith(line,"#")) continue; + if (StringUtils::StartsWith(line,'#')) continue; line = StringUtils::Lower(StringUtils::Trim(line)); @@ -310,10 +310,10 @@ LocalFileFormat::read(std::istream & istream, do { - line = StringUtils::Trim(line); + line = StringUtils::LeftTrim(line); // All lines starting with '#' are comments - if (StringUtils::StartsWith(line,"#")) continue; + if (StringUtils::StartsWith(line,'#')) continue; if (line.empty()) continue; diff --git a/src/OpenColorIO/fileformats/FileFormatIridasItx.cpp b/src/OpenColorIO/fileformats/FileFormatIridasItx.cpp index be903d5d33..ae6282efb8 100755 --- a/src/OpenColorIO/fileformats/FileFormatIridasItx.cpp +++ b/src/OpenColorIO/fileformats/FileFormatIridasItx.cpp @@ -146,7 +146,7 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream, { ++lineNumber; // All lines starting with '#' are comments - if(StringUtils::StartsWith(line,"#")) continue; + if(StringUtils::StartsWith(line,'#')) continue; // Strip, lowercase, and split the line parts = StringUtils::SplitByWhiteSpaces(StringUtils::Lower(StringUtils::Trim(line))); diff --git a/src/OpenColorIO/fileformats/FileFormatPandora.cpp b/src/OpenColorIO/fileformats/FileFormatPandora.cpp index 5018de6ac3..6d36321527 100755 --- a/src/OpenColorIO/fileformats/FileFormatPandora.cpp +++ b/src/OpenColorIO/fileformats/FileFormatPandora.cpp @@ -129,7 +129,7 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream, if(parts.empty()) continue; // Skip all lines starting with '#' - if(StringUtils::StartsWith(parts[0],"#")) continue; + if(StringUtils::StartsWith(parts[0],'#')) continue; if(parts[0] == "channel") { diff --git a/src/OpenColorIO/fileformats/FileFormatResolveCube.cpp b/src/OpenColorIO/fileformats/FileFormatResolveCube.cpp index 9c5e23180e..6b5e0da8d7 100755 --- a/src/OpenColorIO/fileformats/FileFormatResolveCube.cpp +++ b/src/OpenColorIO/fileformats/FileFormatResolveCube.cpp @@ -296,7 +296,7 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream, ++lineNumber; // All lines starting with '#' are comments - if(StringUtils::StartsWith(line,"#")) + if(StringUtils::StartsWith(line,'#')) { if(headerComplete) { diff --git a/src/OpenColorIO/fileformats/FileFormatSpi1D.cpp b/src/OpenColorIO/fileformats/FileFormatSpi1D.cpp index e2e4822b45..1cdc51b22c 100755 --- a/src/OpenColorIO/fileformats/FileFormatSpi1D.cpp +++ b/src/OpenColorIO/fileformats/FileFormatSpi1D.cpp @@ -168,7 +168,7 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream, } } } - while (istream.good() && !StringUtils::StartsWith(headerLine,"{")); + while (istream.good() && !StringUtils::StartsWith(headerLine,'{')); } if (version == -1) diff --git a/src/OpenColorIO/fileformats/FileFormatTruelight.cpp b/src/OpenColorIO/fileformats/FileFormatTruelight.cpp index e3ee605026..0d95261cc3 100755 --- a/src/OpenColorIO/fileformats/FileFormatTruelight.cpp +++ b/src/OpenColorIO/fileformats/FileFormatTruelight.cpp @@ -124,7 +124,7 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream, if(parts.empty()) continue; // Parse header metadata (which starts with #) - if(StringUtils::StartsWith(parts[0],"#")) + if(StringUtils::StartsWith(parts[0],'#')) { if(parts.size() < 2) continue; diff --git a/src/OpenColorIO/fileformats/FileFormatVF.cpp b/src/OpenColorIO/fileformats/FileFormatVF.cpp index 03ff191d5f..9026030344 100755 --- a/src/OpenColorIO/fileformats/FileFormatVF.cpp +++ b/src/OpenColorIO/fileformats/FileFormatVF.cpp @@ -129,7 +129,7 @@ CachedFileRcPtr LocalFileFormat::read(std::istream & istream, if(parts.empty()) continue; - if(StringUtils::StartsWith(parts[0],"#")) continue; + if(StringUtils::StartsWith(parts[0],'#')) continue; if(!in3d) { diff --git a/src/OpenColorIO/fileformats/ctf/CTFTransform.cpp b/src/OpenColorIO/fileformats/ctf/CTFTransform.cpp index a69876fdf8..cf07176568 100644 --- a/src/OpenColorIO/fileformats/ctf/CTFTransform.cpp +++ b/src/OpenColorIO/fileformats/ctf/CTFTransform.cpp @@ -733,7 +733,7 @@ const char * BitDepthToCLFString(BitDepth bitDepth) else if (bitDepth == BIT_DEPTH_F16) return "16f"; else if (bitDepth == BIT_DEPTH_F32) return "32f"; - throw Exception("Bitdepth has been validated before calling this."); + throw Exception("Bitdepth has not been validated before calling this."); } BitDepth GetValidatedFileBitDepth(BitDepth bd, OpData::Type type) @@ -1986,7 +1986,9 @@ void Lut1DWriter::writeContent() const // To avoid needing to duplicate the const objects, // we scale the values on-the-fly while writing. - const float scale = (float)GetBitDepthMaxValue(m_outBitDepth); + const BitDepth arrayBitDepth = m_lut->getDirection() == TRANSFORM_DIR_INVERSE ? + m_inBitDepth : m_outBitDepth; + const float scale = (float) GetBitDepthMaxValue(arrayBitDepth); if (m_lut->isOutputRawHalfs()) { @@ -2016,7 +2018,7 @@ void Lut1DWriter::writeContent() const values.begin(), values.end(), array.getNumColorComponents(), - m_outBitDepth, + arrayBitDepth, array.getNumColorComponents() == 1 ? 3 : 1, scale); } @@ -2110,12 +2112,15 @@ void Lut3DWriter::writeContent() const // To avoid needing to duplicate the const objects, // we scale the values on-the-fly while writing. - const float scale = (float)GetBitDepthMaxValue(m_outBitDepth); + const BitDepth arrayBitDepth = m_lut->getDirection() == TRANSFORM_DIR_INVERSE ? + m_inBitDepth : m_outBitDepth; + const float scale = (float) GetBitDepthMaxValue(arrayBitDepth); + WriteValues(m_formatter, array.getValues().begin(), array.getValues().end(), 3, - m_outBitDepth, + arrayBitDepth, 1, scale); @@ -2508,6 +2513,28 @@ BitDepth GetInputFileBD(ConstOpDataRcPtr op) const auto bd = range->getFileInputBitDepth(); return GetValidatedFileBitDepth(bd, type); } + else if (type == OpData::Lut1DType) + { + // For an InverseLut1D, the file "out" depth, which determines the array scaling, + // must actually be set as the in-depth. + auto lut = OCIO_DYNAMIC_POINTER_CAST(op); + if (lut->getDirection() == TRANSFORM_DIR_INVERSE) + { + const auto bd = lut->getFileOutputBitDepth(); + return GetValidatedFileBitDepth(bd, type); + } + } + else if (type == OpData::Lut3DType) + { + // For an InverseLut3D, the file "out" depth, which determines the array scaling, + // must actually be set as the in-depth. + auto lut = OCIO_DYNAMIC_POINTER_CAST(op); + if (lut->getDirection() == TRANSFORM_DIR_INVERSE) + { + const auto bd = lut->getFileOutputBitDepth(); + return GetValidatedFileBitDepth(bd, type); + } + } return BIT_DEPTH_F32; } @@ -2515,6 +2542,9 @@ BitDepth GetInputFileBD(ConstOpDataRcPtr op) void TransformWriter::writeOps(const CTFVersion & version) const { + // If the ops have a specific file input/output bit-depth that was set (e.g., if the + // ops were originally read in from a CTF/CLF file), then we try to preserve those + // values on write. Otherwise, default to 32f. BitDepth inBD = BIT_DEPTH_F32; BitDepth outBD = BIT_DEPTH_F32; @@ -2523,15 +2553,27 @@ void TransformWriter::writeOps(const CTFVersion & version) const size_t numSavedOps = 0; if (numOps) { + // Initialize the input bit-depth from the first op. Thereafter, the input depth + // will be determined from the output depth of the previous op that was written. inBD = GetInputFileBD(ops[0]); + for (size_t i = 0; i < numOps; ++i) { auto & op = ops[i]; + // Each op is allowed to set its preferred output depth, so that is always + // respected. However, we try to honour preferred input depth too, where possible. + // If the next op has a preferred file input bit-depth, use that as the default + // out-depth for the current op. However, if the current op has a preferred + // file out depth, that will take precedence below. If an op's preferred file + // depth cannot be honoured, it still results in a perfectly valid CTF file, + // it's only that the scaling of array values may be adjusted relative to the + // original CTF file it was created from (if any). + // See FileFormatCTF_tests.cpp test bitdepth_ctf. if (i + 1 < numOps) { auto & nextOp = ops[i + 1]; - // Return file input bit-depth for Matrix & Range, F32 for others. + // Return file input bit-depth for ops with a preference, F32 for others. outBD = GetInputFileBD(nextOp); } @@ -2688,7 +2730,11 @@ void TransformWriter::writeOps(const CTFVersion & version) const // Avoid copying LUT, write will take bit-depth into account. Lut1DWriter opWriter(m_formatter, lut); - outBD = GetValidatedFileBitDepth(lut->getFileOutputBitDepth(), type); + if (lut->getDirection() == TRANSFORM_DIR_FORWARD) + { + // For an inverse Lut1D, the fileOutDepth is used for the inBitDepth above. + outBD = GetValidatedFileBitDepth(lut->getFileOutputBitDepth(), type); + } opWriter.setInputBitdepth(inBD); opWriter.setOutputBitdepth(outBD); @@ -2708,7 +2754,11 @@ void TransformWriter::writeOps(const CTFVersion & version) const // Avoid copying LUT, write will take bit-depth into account. Lut3DWriter opWriter(m_formatter, lut); - outBD = GetValidatedFileBitDepth(lut->getFileOutputBitDepth(), type); + if (lut->getDirection() == TRANSFORM_DIR_FORWARD) + { + // For an inverse Lut3D, the fileOutDepth is used for the inBitDepth above. + outBD = GetValidatedFileBitDepth(lut->getFileOutputBitDepth(), type); + } opWriter.setInputBitdepth(inBD); opWriter.setOutputBitdepth(outBD); diff --git a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpGPU.cpp b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpGPU.cpp index 30c831544e..1b4ba870dd 100644 --- a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpGPU.cpp +++ b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpGPU.cpp @@ -411,7 +411,7 @@ std::string _Add_Reach_table( std::ostringstream resName; resName << shaderCreator->getResourcePrefix() << std::string("_") - << std::string("reach_m_table") + << std::string("reach_m_table_") << resourceIndex; // Note: Remove potentially problematic double underscores from GLSL resource names. @@ -684,7 +684,7 @@ std::string _Add_Cusp_table( std::ostringstream resName; resName << shaderCreator->getResourcePrefix() << std::string("_") - << std::string("gamut_cusp_table") + << std::string("gamut_cusp_table_") << resourceIndex; // Note: Remove potentially problematic double underscores from GLSL resource names. @@ -803,7 +803,7 @@ std::string _Add_Gamma_table( std::ostringstream resName; resName << shaderCreator->getResourcePrefix() << std::string("_") - << std::string("upper_hull_gamma_table") + << std::string("upper_hull_gamma_table_") << resourceIndex; // Note: Remove potentially problematic double underscores from GLSL resource names. diff --git a/src/OpenColorIO/transforms/builtins/Displays.cpp b/src/OpenColorIO/transforms/builtins/Displays.cpp index e2bfdddd35..bd6f0c5e7c 100644 --- a/src/OpenColorIO/transforms/builtins/Displays.cpp +++ b/src/OpenColorIO/transforms/builtins/Displays.cpp @@ -358,6 +358,12 @@ void RegisterAll(BuiltinTransformRegistryImpl & registry) noexcept registry.addBuiltin("DISPLAY - CIE-XYZ-D65_to_DisplayP3", "Convert CIE XYZ (D65 white) to Apple Display P3", CIE_XYZ_D65_to_DisplayP3_Functor); + + // NOTE: This builtin is defined to be able to partition SDR and HDR view transforms under two separate + // displays rather than a single one. + registry.addBuiltin("DISPLAY - CIE-XYZ-D65_to_DisplayP3-HDR", + "Convert CIE XYZ (D65 white) to Apple Display P3 (HDR)", + CIE_XYZ_D65_to_DisplayP3_Functor); } { diff --git a/src/apps/ociodisplay/main.cpp b/src/apps/ociodisplay/main.cpp index d464abcf02..ed7f09759a 100644 --- a/src/apps/ociodisplay/main.cpp +++ b/src/apps/ociodisplay/main.cpp @@ -1,7 +1,6 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright Contributors to the OpenColorIO Project. - #include #include #include @@ -37,11 +36,11 @@ namespace OCIO = OCIO_NAMESPACE; #include "oglapp.h" #include "imageio.h" -bool g_verbose = false; +bool g_verbose = false; bool g_gpulegacy = false; -bool g_gpuinfo = false; +bool g_gpuinfo = false; #if __APPLE__ -bool g_useMetal = false; +bool g_useMetal = false; #endif std::string g_filename; @@ -52,22 +51,21 @@ std::string g_inputColorSpace; std::string g_display; std::string g_transformName; std::string g_look; -OCIO::OptimizationFlags g_optimization{ OCIO::OPTIMIZATION_DEFAULT }; +OCIO::OptimizationFlags g_optimization{OCIO::OPTIMIZATION_DEFAULT}; -static const std::array, 5> OptmizationMenu = { { - { "None", OCIO::OPTIMIZATION_NONE }, - { "Lossless", OCIO::OPTIMIZATION_LOSSLESS }, - { "Very good", OCIO::OPTIMIZATION_VERY_GOOD }, - { "Good", OCIO::OPTIMIZATION_GOOD }, - { "Draft", OCIO::OPTIMIZATION_DRAFT } } }; +static const std::array, 5> OptmizationMenu = {{{"None", OCIO::OPTIMIZATION_NONE}, + {"Lossless", OCIO::OPTIMIZATION_LOSSLESS}, + {"Very good", OCIO::OPTIMIZATION_VERY_GOOD}, + {"Good", OCIO::OPTIMIZATION_GOOD}, + {"Draft", OCIO::OPTIMIZATION_DRAFT}}}; -float g_exposure_fstop{ 0.0f }; -float g_display_gamma{ 1.0f }; -int g_channelHot[4]{ 1, 1, 1, 1 }; // show rgb +float g_exposure_fstop{0.0f}; +float g_display_gamma{1.0f}; +int g_channelHot[4]{1, 1, 1, 1}; // show rgb +int g_viewsMenuID; OCIO::OglAppRcPtr g_oglApp; - void UpdateOCIOGLState(); static void InitImageTexture(const char * filename) @@ -82,7 +80,7 @@ static void InitImageTexture(const char * filename) { img.read(filename, OCIO::BIT_DEPTH_F32); } - catch (const std::exception & e) + catch (const std::exception &e) { std::cerr << "ERROR: Loading file failed: " << e.what() << std::endl; exit(1); @@ -100,19 +98,19 @@ static void InitImageTexture(const char * filename) img.init(512, 512, OCIO::CHANNEL_ORDERING_RGBA, OCIO::BIT_DEPTH_F32); - float * pixels = (float *) img.getData(); + float * pixels = (float *)img.getData(); const long width = img.getWidth(); const long channels = img.getNumChannels(); - for (int y=0; yinitImage(img.getWidth(), img.getHeight(), comp, - (float*) img.getData()); + (float *)img.getData()); } - } void InitOCIO(const char * filename) @@ -167,7 +164,7 @@ void InitOCIO(const char * filename) } else { - std::cout << "colorspace: " << g_inputColorSpace + std::cout << "colorspace: " << g_inputColorSpace << " \t(could not determine from filename, using default)" << std::endl; } @@ -251,34 +248,34 @@ static void Key(unsigned char key, int /*x*/, int /*y*/) static void SpecialKey(int key, int x, int y) { - (void) x; - (void) y; + (void)x; + (void)y; int mod = glutGetModifiers(); - if(key == GLUT_KEY_UP && (mod & GLUT_ACTIVE_CTRL)) + if (key == GLUT_KEY_UP && (mod & GLUT_ACTIVE_CTRL)) { g_exposure_fstop += 0.25f; } - else if(key == GLUT_KEY_DOWN && (mod & GLUT_ACTIVE_CTRL)) + else if (key == GLUT_KEY_DOWN && (mod & GLUT_ACTIVE_CTRL)) { g_exposure_fstop -= 0.25f; } - else if(key == GLUT_KEY_HOME && (mod & GLUT_ACTIVE_CTRL)) + else if (key == GLUT_KEY_HOME && (mod & GLUT_ACTIVE_CTRL)) { g_exposure_fstop = 0.0f; g_display_gamma = 1.0f; } - else if(key == GLUT_KEY_UP && (mod & GLUT_ACTIVE_ALT)) + else if (key == GLUT_KEY_UP && (mod & GLUT_ACTIVE_ALT)) { g_display_gamma *= 1.1f; } - else if(key == GLUT_KEY_DOWN && (mod & GLUT_ACTIVE_ALT)) + else if (key == GLUT_KEY_DOWN && (mod & GLUT_ACTIVE_ALT)) { g_display_gamma /= 1.1f; } - else if(key == GLUT_KEY_HOME && (mod & GLUT_ACTIVE_ALT)) + else if (key == GLUT_KEY_HOME && (mod & GLUT_ACTIVE_ALT)) { g_exposure_fstop = 0.0f; g_display_gamma = 1.0f; @@ -289,6 +286,22 @@ static void SpecialKey(int key, int x, int y) glutPostRedisplay(); } +void updateViewsMenu(const char * displayValue) +{ + + OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); + glutSetMenu(g_viewsMenuID); + int numViewsMenuItems = glutGet(GLUT_MENU_NUM_ITEMS); + for (int i = numViewsMenuItems; i > 0; --i) + { + glutRemoveMenuItem(i); + } + for (int i = 0; i < config->getNumViews(displayValue); ++i) + { + glutAddMenuEntry(config->getView(displayValue, i), i); + } +} + void UpdateOCIOGLState() { if (!g_oglApp) @@ -300,9 +313,9 @@ void UpdateOCIOGLState() OCIO::ConstConfigRcPtr config = OCIO::GetCurrentConfig(); OCIO::DisplayViewTransformRcPtr transform = OCIO::DisplayViewTransform::Create(); - transform->setSrc( g_inputColorSpace.c_str() ); - transform->setDisplay( g_display.c_str() ); - transform->setView( g_transformName.c_str() ); + transform->setSrc(g_inputColorSpace.c_str()); + transform->setDisplay(g_display.c_str()); + transform->setView(g_transformName.c_str()); OCIO::LegacyViewingPipelineRcPtr vp = OCIO::LegacyViewingPipeline::Create(); vp->setDisplayViewTransform(transform); @@ -314,37 +327,37 @@ void UpdateOCIOGLState() std::cout << std::endl; std::cout << "Color transformation composed of:" << std::endl; std::cout << " Image ColorSpace is:\t" << g_inputColorSpace << std::endl; - std::cout << " Transform is:\t\t" << g_transformName << std::endl; - std::cout << " Device is:\t\t" << g_display << std::endl; + std::cout << " Views is:\t\t" << g_transformName << std::endl; + std::cout << " Display is:\t\t" << g_display << std::endl; std::cout << " Looks Override is:\t'" << g_look << "'" << std::endl; std::cout << " with:" << std::endl; std::cout << " exposure_fstop = " << g_exposure_fstop << std::endl; std::cout << " display_gamma = " << g_display_gamma << std::endl; - std::cout << " channels = " + std::cout << " channels = " << (g_channelHot[0] ? "R" : "") << (g_channelHot[1] ? "G" : "") << (g_channelHot[2] ? "B" : "") << (g_channelHot[3] ? "A" : "") << std::endl; - for (const auto & opt : OptmizationMenu) + for (const auto &opt : OptmizationMenu) { if (opt.second == g_optimization) { - std::cout << std::endl << "Optimization: " << opt.first << std::endl; + std::cout << std::endl + << "Optimization: " << opt.first << std::endl; } } - } // Add optional transforms to create a full-featured, "canonical" display pipeline // Fstop exposure control (in SCENE_LINEAR) { double gain = powf(2.0f, g_exposure_fstop); - const double slope4f[] = { gain, gain, gain, gain }; + const double slope4f[] = {gain, gain, gain, gain}; double m44[16]; double offset4[4]; OCIO::MatrixTransform::Scale(m44, offset4, slope4f); - OCIO::MatrixTransformRcPtr mtx = OCIO::MatrixTransform::Create(); + OCIO::MatrixTransformRcPtr mtx = OCIO::MatrixTransform::Create(); mtx->setMatrix(m44); mtx->setOffset(offset4); vp->setLinearCC(mtx); @@ -365,9 +378,9 @@ void UpdateOCIOGLState() // Post-display transform gamma { - double exponent = 1.0/std::max(1e-6, static_cast(g_display_gamma)); - const double exponent4f[4] = { exponent, exponent, exponent, exponent }; - OCIO::ExponentTransformRcPtr expTransform = OCIO::ExponentTransform::Create(); + double exponent = 1.0 / std::max(1e-6, static_cast(g_display_gamma)); + const double exponent4f[4] = {exponent, exponent, exponent, exponent}; + OCIO::ExponentTransformRcPtr expTransform = OCIO::ExponentTransform::Create(); expTransform->setValue(exponent4f); vp->setDisplayCC(expTransform); } @@ -377,7 +390,7 @@ void UpdateOCIOGLState() { processor = vp->getProcessor(config, config->getCurrentContext()); } - catch (const OCIO::Exception & e) + catch (const OCIO::Exception &e) { std::cerr << e.what() << std::endl; return; @@ -391,17 +404,15 @@ void UpdateOCIOGLState() OCIO::GpuShaderDescRcPtr shaderDesc = OCIO::GpuShaderDesc::CreateShaderDesc(); shaderDesc->setLanguage( #if __APPLE__ - g_useMetal ? - OCIO::GPU_LANGUAGE_MSL_2_0 : + g_useMetal ? OCIO::GPU_LANGUAGE_MSL_2_0 : #endif - OCIO::GPU_LANGUAGE_GLSL_1_2); + OCIO::GPU_LANGUAGE_GLSL_1_2); shaderDesc->setFunctionName("OCIODisplay"); shaderDesc->setResourcePrefix("ocio_"); // Extract the shader information. - OCIO::ConstGPUProcessorRcPtr gpu - = g_gpulegacy ? processor->getOptimizedLegacyGPUProcessor(g_optimization, 32) - : processor->getOptimizedGPUProcessor(g_optimization); + OCIO::ConstGPUProcessorRcPtr gpu = g_gpulegacy ? processor->getOptimizedLegacyGPUProcessor(g_optimization, 32) + : processor->getOptimizedGPUProcessor(g_optimization); gpu->extractGpuShaderInfo(shaderDesc); g_oglApp->setShader(shaderDesc); @@ -446,6 +457,7 @@ void displayDevice_CB(int id) g_look = config->getDisplayViewLooks(g_display.c_str(), g_transformName.c_str()); + updateViewsMenu(display); UpdateOCIOGLState(); glutPostRedisplay(); } @@ -499,7 +511,7 @@ static void PopulateOCIOMenus() int csMenuID = glutCreateMenu(imageColorSpace_CB); std::map families; - for (int i=0; igetNumColorSpaces(); ++i) + for (int i = 0; i < config->getNumColorSpaces(); ++i) { const char * csName = config->getColorSpaceNameByIndex(i); if (csName && *csName) @@ -510,7 +522,7 @@ static void PopulateOCIOMenus() const char * family = cs->getFamily(); if (family && *family) { - if (families.find(family)==families.end()) + if (families.find(family) == families.end()) { families[family] = glutCreateMenu(imageColorSpace_CB); glutAddMenuEntry(csName, i); @@ -533,35 +545,32 @@ static void PopulateOCIOMenus() } } - int deviceMenuID = glutCreateMenu(displayDevice_CB); - for (int i=0; igetNumDisplays(); ++i) + int displayMenuID = glutCreateMenu(displayDevice_CB); + for (int i = 0; i < config->getNumDisplays(); ++i) { glutAddMenuEntry(config->getDisplay(i), i); } - int transformMenuID = glutCreateMenu(transform_CB); + g_viewsMenuID = glutCreateMenu(transform_CB); const char * defaultDisplay = config->getDefaultDisplay(); - for (int i=0; igetNumViews(defaultDisplay); ++i) - { - glutAddMenuEntry(config->getView(defaultDisplay, i), i); - } + updateViewsMenu(defaultDisplay); int lookMenuID = glutCreateMenu(look_CB); - for (int i=0; igetNumLooks(); ++i) + for (int i = 0; i < config->getNumLooks(); ++i) { glutAddMenuEntry(config->getLookNameByIndex(i), i); } int optimizationMenuID = glutCreateMenu(optimization_CB); - for (size_t i = 0; i(i)); } glutCreateMenu(menuCallback); glutAddSubMenu("Image ColorSpace", csMenuID); - glutAddSubMenu("Transform", transformMenuID); - glutAddSubMenu("Device", deviceMenuID); + glutAddSubMenu("Views", g_viewsMenuID); + glutAddSubMenu("Display", displayMenuID); glutAddSubMenu("Looks Override", lookMenuID); glutAddSubMenu("Optimization", optimizationMenuID); @@ -569,49 +578,49 @@ static void PopulateOCIOMenus() } const char * USAGE_TEXT = "\n" -"Keys:\n" -"\tCtrl+Up: Exposure +1/4 stop (in scene linear)\n" -"\tCtrl+Down: Exposure -1/4 stop (in scene linear)\n" -"\tCtrl+Home: Reset Exposure + Gamma\n" -"\n" -"\tAlt+Up: Gamma up (post display transform)\n" -"\tAlt+Down: Gamma down (post display transform)\n" -"\tAlt+Home: Reset Exposure + Gamma\n" -"\n" -"\tC: View Color\n" -"\tR: View Red\n" -"\tG: View Green\n" -"\tB: View Blue\n" -"\tA: View Alpha\n" -"\tL: View Luma\n" -"\n" -"\tRight-Mouse Button: Configure Display / Transform / ColorSpace / Looks / Optimization\n" -"\n" -"\tEsc: Quit\n"; + "Keys:\n" + "\tCtrl+Up: Exposure +1/4 stop (in scene linear)\n" + "\tCtrl+Down: Exposure -1/4 stop (in scene linear)\n" + "\tCtrl+Home: Reset Exposure + Gamma\n" + "\n" + "\tAlt+Up: Gamma up (post display transform)\n" + "\tAlt+Down: Gamma down (post display transform)\n" + "\tAlt+Home: Reset Exposure + Gamma\n" + "\n" + "\tC: View Color\n" + "\tR: View Red\n" + "\tG: View Green\n" + "\tB: View Blue\n" + "\tA: View Alpha\n" + "\tL: View Luma\n" + "\n" + "\tRight-Mouse Button: Configure Display / Transform / ColorSpace / Looks / Optimization\n" + "\n" + "\tEsc: Quit\n"; void parseArguments(int argc, char **argv) { - for (int i=1; i("ociodisplay", 512, 512); } } - catch (const OCIO::Exception & e) + catch (const OCIO::Exception &e) { std::cerr << e.what() << std::endl; return 1; @@ -703,8 +712,8 @@ int main(int argc, char **argv) { std::cout << std::endl; std::cout << "OCIO Config. file : '" << env << "'" << std::endl; - std::cout << "OCIO Config. version: " << config->getMajorVersion() << "." - << config->getMinorVersion() << std::endl; + std::cout << "OCIO Config. version: " << config->getMajorVersion() << "." + << config->getMinorVersion() << std::endl; std::cout << "OCIO search_path : " << config->getSearchPath() << std::endl; } } @@ -717,7 +726,7 @@ int main(int argc, char **argv) { InitOCIO(g_filename.c_str()); } - catch(OCIO::Exception & e) + catch (OCIO::Exception &e) { std::cerr << e.what() << std::endl; exit(1); @@ -729,7 +738,7 @@ int main(int argc, char **argv) { UpdateOCIOGLState(); } - catch (const OCIO::Exception & e) + catch (const OCIO::Exception &e) { std::cerr << e.what() << std::endl; exit(1); diff --git a/src/bindings/python/package/__init__.py b/src/bindings/python/package/__init__.py index 800c7968ae..e5fa6868f2 100644 --- a/src/bindings/python/package/__init__.py +++ b/src/bindings/python/package/__init__.py @@ -24,13 +24,15 @@ # 2 - Checking that the directories exist and are not "." # 3 - Add them to the DLL load path. # -# The behavior described above is opt-out which means that it is activated by default. -# A user can opt-out and use the default behavior of Python 3.8+ by setting OCIO_PYTHON_LOAD_DLLS_FROM_PATH -# environment variable to 0. +# The behavior described above is opt-in which means that it is deactivated by default. +# A user can opt-in by setting OCIO_PYTHON_LOAD_DLLS_FROM_PATH environment variable to 1. +# Please note that OCIO 2.4.0 and earlier had this behavior opt-out but it was changed +# in 2.4.1 after it was found problematic in uncontrolled environment (causing segfault +# on PyOpenColorIO import) as well as for security reasons. # if sys.version_info >= (3, 8) and platform.system() == "Windows": - if os.getenv("OCIO_PYTHON_LOAD_DLLS_FROM_PATH", "1") == "1": + if os.getenv("OCIO_PYTHON_LOAD_DLLS_FROM_PATH", "0") == "1": for path in os.getenv("PATH", "").split(os.pathsep): if os.path.exists(path) and path != ".": os.add_dll_directory(path) diff --git a/src/libutils/oglapphelpers/glsl.cpp b/src/libutils/oglapphelpers/glsl.cpp index 16709fa3e7..0154effa35 100644 --- a/src/libutils/oglapphelpers/glsl.cpp +++ b/src/libutils/oglapphelpers/glsl.cpp @@ -468,7 +468,7 @@ std::string OpenGLBuilder::getGLSLVersionString() case GPU_LANGUAGE_GLSL_ES_3_0: return "#version 300 es"; case GPU_LANGUAGE_CG: - case GPU_LANGUAGE_HLSL_DX11: + case GPU_LANGUAGE_HLSL_SM_5_0: case LANGUAGE_OSL_1: default: // These are all impossible in OpenGL contexts. diff --git a/src/utils/NumberUtils.h b/src/utils/NumberUtils.h index f6835a0a16..e6b273f449 100644 --- a/src/utils/NumberUtils.h +++ b/src/utils/NumberUtils.h @@ -4,6 +4,22 @@ #ifndef INCLUDED_NUMBERUTILS_H #define INCLUDED_NUMBERUTILS_H +// With C++17, we can use from_chars. +// +// That has advantage of not dealing with locale / errno, +// which might involve locks or thread local storage accesses. +// +// Note that it is not enough to check __cplusplus version, +// since even if compiler reports C++17 it might not implement +// from_chars for floats. Checking __cpp_lib_to_chars works +// correctly and the check starts passing with gcc 11, clang 12, +// msvc 2019 (16.4). It correctly does not pass on apple clang 15, +// since it does not implement from_chars for floats. +#if __cpp_lib_to_chars >= 201611L +#define USE_CHARCONV_FROM_CHARS +#include +#endif + #if defined(_MSC_VER) #define really_inline __forceinline #else @@ -54,14 +70,56 @@ struct from_chars_result static const Locale loc; +#ifdef USE_CHARCONV_FROM_CHARS +really_inline bool from_chars_is_space(char c) noexcept +{ + return c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\v' || c == '\f'; +} + +// skip prefix whitespace and "+" +really_inline const char* from_chars_skip_prefix(const char* first, const char* last) noexcept +{ + while (first < last && from_chars_is_space(first[0])) + { + ++first; + } + if (first < last && first[0] == '+') + { + ++first; + } + return first; +} + +really_inline bool from_chars_hex_prefix(const char*& first, const char* last) noexcept +{ + if (first + 2 < last && first[0] == '0' && (first[1] == 'x' || first[1] == 'X')) + { + first += 2; + return true; + } + return false; +} +#endif + really_inline from_chars_result from_chars(const char *first, const char *last, double &value) noexcept { - errno = 0; if (!first || !last || first == last) { return {first, std::errc::invalid_argument}; } +#ifdef USE_CHARCONV_FROM_CHARS + first = from_chars_skip_prefix(first, last); + std::chars_format fmt = std::chars_format::general; + if (from_chars_hex_prefix(first, last)) + { + fmt = std::chars_format::hex; + } + std::from_chars_result res = std::from_chars(first, last, value, fmt); + return from_chars_result{ res.ptr, res.ec }; +#else + + errno = 0; char * endptr = nullptr; double @@ -88,16 +146,28 @@ really_inline from_chars_result from_chars(const char *first, const char *last, { return {first, std::errc::argument_out_of_domain}; } +#endif } really_inline from_chars_result from_chars(const char *first, const char *last, float &value) noexcept { - errno = 0; if (!first || !last || first == last) { return {first, std::errc::invalid_argument}; } +#ifdef USE_CHARCONV_FROM_CHARS + first = from_chars_skip_prefix(first, last); + std::chars_format fmt = std::chars_format::general; + if (from_chars_hex_prefix(first, last)) + { + fmt = std::chars_format::hex; + } + std::from_chars_result res = std::from_chars(first, last, value, fmt); + return from_chars_result{ res.ptr, res.ec }; +#else + + errno = 0; char *endptr = nullptr; float @@ -132,16 +202,28 @@ really_inline from_chars_result from_chars(const char *first, const char *last, { return {first, std::errc::argument_out_of_domain}; } +#endif } really_inline from_chars_result from_chars(const char *first, const char *last, long int &value) noexcept { - errno = 0; if (!first || !last || first == last) { return {first, std::errc::invalid_argument}; } +#ifdef USE_CHARCONV_FROM_CHARS + first = from_chars_skip_prefix(first, last); + int base = 10; + if (from_chars_hex_prefix(first, last)) + { + base = 16; + } + std::from_chars_result res = std::from_chars(first, last, value, base); + return from_chars_result{ res.ptr, res.ec }; +#else + + errno = 0; char *endptr = nullptr; long int @@ -170,6 +252,7 @@ really_inline from_chars_result from_chars(const char *first, const char *last, { return {first, std::errc::argument_out_of_domain}; } +#endif } } // namespace NumberUtils } // namespace OCIO_NAMESPACE diff --git a/src/utils/StringUtils.h b/src/utils/StringUtils.h index cc1cf4cd43..7dbfaa1a4b 100644 --- a/src/utils/StringUtils.h +++ b/src/utils/StringUtils.h @@ -44,6 +44,13 @@ inline unsigned char Upper(unsigned char c) } } +// Checks if character is whitespace (space, tab, newline or other +// non-printable char). Does not take locale into account. +inline bool IsSpace(unsigned char c) +{ + return c <= ' '; +} + // Return the lower case string. inline std::string Lower(std::string str) { @@ -95,6 +102,13 @@ inline bool StartsWith(const std::string & str, const std::string & prefix) return str.size() >= prefix.size() && 0 == str.compare(0, prefix.size(), prefix); } +// Return true if the string starts with the prefix character. +// Note: The comparison is case sensitive. +inline bool StartsWith(const std::string& str, char prefix) +{ + return !str.empty() && str.front() == prefix; +} + // Starting from the left, trim the character. inline std::string LeftTrim(std::string str, char c) { @@ -106,7 +120,7 @@ inline std::string LeftTrim(std::string str, char c) // Starting from the left, trim all the space characters i.e. space, tabulation, etc. inline std::string LeftTrim(std::string str) { - const auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !std::isspace(static_cast(ch)); }); + const auto it = std::find_if(str.begin(), str.end(), [](char ch) { return !IsSpace(ch); }); str.erase(str.begin(), it); return str; } @@ -123,7 +137,7 @@ inline std::string RightTrim(std::string str, char c) inline std::string RightTrim(std::string str) { const auto it = - std::find_if(str.rbegin(), str.rend(), [](char ch) { return !std::isspace(static_cast(ch)); }); + std::find_if(str.rbegin(), str.rend(), [](char ch) { return !IsSpace(ch); }); str.erase(it.base(), str.end()); return str; } @@ -148,6 +162,12 @@ inline void Trim(StringVec & list) } } +// Checks if string is empty or white-space only. +inline bool IsEmptyOrWhiteSpace(const std::string& str) +{ + return std::find_if(str.begin(), str.end(), [](char ch) { return !IsSpace(ch); }) == str.end(); +} + // Split a string content using an arbitrary separator. inline StringVec Split(const std::string & str, char separator) { diff --git a/tests/cpu/Context_tests.cpp b/tests/cpu/Context_tests.cpp index c8d4052ffd..3372d8f36f 100644 --- a/tests/cpu/Context_tests.cpp +++ b/tests/cpu/Context_tests.cpp @@ -119,8 +119,8 @@ OCIO_ADD_TEST(Context, use_searchpaths) const std::string res1 = searchPath1 + "/Context.cpp"; OCIO_CHECK_ASSERT(strcmp(SanitizePath(resolvedSource.c_str()).c_str(), SanitizePath(res1.c_str()).c_str()) == 0); - OCIO_CHECK_NO_THROW(resolvedSource = context->resolveFileLocation("GPUHelpers.h")); - const std::string res2 = searchPath2 + "/GPUHelpers.h"; + OCIO_CHECK_NO_THROW(resolvedSource = context->resolveFileLocation("GPUUnitTest.h")); + const std::string res2 = searchPath2 + "/GPUUnitTest.h"; OCIO_CHECK_ASSERT(strcmp(SanitizePath(resolvedSource.c_str()).c_str(), SanitizePath(res2.c_str()).c_str()) == 0); } @@ -141,8 +141,8 @@ OCIO_ADD_TEST(Context, use_searchpaths_workingdir) const std::string res1 = ociodir + "/" + searchPath1 + "/Context.cpp"; OCIO_CHECK_ASSERT(strcmp(SanitizePath(resolvedSource.c_str()).c_str(), SanitizePath(res1.c_str()).c_str()) == 0); - OCIO_CHECK_NO_THROW(resolvedSource = context->resolveFileLocation("GPUHelpers.h")); - const std::string res2 = ociodir + "/" + searchPath2 + "/GPUHelpers.h"; + OCIO_CHECK_NO_THROW(resolvedSource = context->resolveFileLocation("GPUUnitTest.h")); + const std::string res2 = ociodir + "/" + searchPath2 + "/GPUUnitTest.h"; OCIO_CHECK_ASSERT(strcmp(SanitizePath(resolvedSource.c_str()).c_str(), SanitizePath(res2.c_str()).c_str()) == 0); } diff --git a/tests/cpu/OpOptimizers_tests.cpp b/tests/cpu/OpOptimizers_tests.cpp index 6f606fd6e8..6806ccba09 100644 --- a/tests/cpu/OpOptimizers_tests.cpp +++ b/tests/cpu/OpOptimizers_tests.cpp @@ -311,6 +311,8 @@ OCIO_ADD_TEST(OpOptimizers, combine_ops) OCIO::CombineOps(ops, AllBut(OCIO::OPTIMIZATION_COMP_MATRIX)); OCIO_CHECK_EQUAL(ops.size(), 3); OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); + // CombineOps removes at most one pair on each call, repeat to combine all pairs. + OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); OCIO_CHECK_EQUAL(ops.size(), 1); } @@ -351,6 +353,10 @@ OCIO_ADD_TEST(OpOptimizers, combine_ops) OCIO::CombineOps(ops, AllBut(OCIO::OPTIMIZATION_COMP_MATRIX)); OCIO_CHECK_EQUAL(ops.size(), 5); OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); + // CombineOps removes at most one pair on each call, repeat to combine all pairs. + OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); + OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); + OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); OCIO_CHECK_EQUAL(ops.size(), 1); } @@ -366,10 +372,57 @@ OCIO_ADD_TEST(OpOptimizers, combine_ops) OCIO::CombineOps(ops, AllBut(OCIO::OPTIMIZATION_COMP_MATRIX)); OCIO_CHECK_EQUAL(ops.size(), 4); OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); + // CombineOps removes at most one pair on each call, repeat to combine all pairs. + OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); OCIO_CHECK_EQUAL(ops.size(), 0); } } +OCIO_ADD_TEST(OpOptimizers, prefer_pair_inverse_over_combine) +{ + // When a pair of forward / inverse LUTs with non 0 to 1 domain are used + // as process space for a Look (eg. CDL), the Optimizer tries to combine + // them when the Look results in a no-op. Here we make sure this result + // in an appropriate clamp instead of a new half-domain LUT resulting + // from the naive composition of the two LUTs. + + OCIO::OpRcPtrVec ops; + + // This spi1d uses "From -1.0 2.0", so the forward direction would become a + // Matrix to do the scaling followed by a Lut1D, and the inverse is a Lut1D + // followed by a Matrix. Note that although the matrices compose into an identity, + // they are both forward direction and *not* pair inverses of each other. + const std::string fileName("lut1d_4.spi1d"); + OCIO::ContextRcPtr context = OCIO::Context::Create(); + OCIO_CHECK_NO_THROW(OCIO::BuildOpsTest(ops, fileName, context, + OCIO::TRANSFORM_DIR_INVERSE)); + + // The default negativeStyle is basicPassThruFwd, hence this op will be + // removed as a no-op on the first optimization pass. + const double exp_null[4] = {1.0, 1.0, 1.0, 1.0}; + OCIO::CreateExponentOp(ops, exp_null, OCIO::TRANSFORM_DIR_FORWARD); + + OCIO_CHECK_NO_THROW(OCIO::BuildOpsTest(ops, fileName, context, + OCIO::TRANSFORM_DIR_FORWARD)); + + OCIO_CHECK_NO_THROW(ops.finalize()); + OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_ALL)); + // The starting list of ops is this: + // FileNoOp --> Lut1D --> Matrix --> Gamma --> FileNoOp --> Matrix --> Lut1D + // This becomes the following on the first pass after no-ops are removed: + // Lut1D --> Matrix --> Matrix --> Lut1D + // The matrices are combined and removed on the first pass, leaving this: + // Lut1D --> Lut1D + // Second pass: the LUTs are identified as a pair of inverses and replaced with a Range: + // Range + + OCIO_CHECK_EQUAL(ops.size(), 1); + OCIO::ConstOpRcPtr op = ops[0]; + OCIO_REQUIRE_ASSERT(op); + auto range = OCIO_DYNAMIC_POINTER_CAST(op->data()); + OCIO_REQUIRE_ASSERT(range); +} + OCIO_ADD_TEST(OpOptimizers, non_optimizable) { OCIO::OpRcPtrVec ops; @@ -993,7 +1046,6 @@ OCIO_ADD_TEST(OpOptimizers, gamma_comp) OCIO::OpRcPtrVec optOps = ops.clone(); OCIO::OpRcPtrVec optOps_noComp = ops.clone(); - OCIO::OpRcPtrVec optOps_noIdentity = ops.clone(); OCIO_CHECK_EQUAL(optOps_noComp.size(), 4); OCIO_CHECK_NO_THROW(optOps_noComp.finalize()); @@ -1006,16 +1058,6 @@ OCIO_ADD_TEST(OpOptimizers, gamma_comp) CompareRender(ops, optOps_noComp, __LINE__, 1e-6f); - OCIO_CHECK_EQUAL(optOps_noIdentity.size(), 4); - OCIO_CHECK_NO_THROW(optOps_noIdentity.finalize()); - OCIO_CHECK_NO_THROW(optOps_noIdentity.optimize(AllBut(OCIO::OPTIMIZATION_IDENTITY_GAMMA))); - // Identity matrix is removed and gammas are combined into an identity gamma - // but it is not removed. - OCIO_CHECK_EQUAL(optOps_noIdentity.size(), 1); - OCIO_CHECK_EQUAL(optOps_noIdentity[0]->getInfo(), ""); - - CompareRender(ops, optOps_noIdentity, __LINE__, 5e-5f); - OCIO_CHECK_NO_THROW(optOps.finalize()); OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT)); @@ -1038,6 +1080,10 @@ OCIO_ADD_TEST(OpOptimizers, gamma_comp_identity) auto gamma1 = std::make_shared(OCIO::GammaOpData::BASIC_FWD, params1, params1, params1, paramsA); + // Note that gamma2 is not a pair inverse of gamma1, it is another FWD gamma where the + // parameter is an inverse. Therefore it won't get replaced as a pair inverse, it must + // be composed into an identity, which may then be replaced. Since the BASIC_FWD style + // clamps negatives, it is replaced with a Range. OCIO::GammaOpData::Params params2 = { 1. / 0.45 }; auto gamma2 = std::make_shared(OCIO::GammaOpData::BASIC_FWD, @@ -1049,17 +1095,29 @@ OCIO_ADD_TEST(OpOptimizers, gamma_comp_identity) OCIO_CHECK_NO_THROW(ops.finalize()); OCIO_CHECK_EQUAL(ops.size(), 2); - OCIO::OpRcPtrVec optOps = ops.clone(); + { + OCIO::OpRcPtrVec optOps = ops.clone(); - // BASIC gamma are composed resulting into identity, that get optimized as a range. - OCIO_CHECK_NO_THROW(optOps.finalize()); - OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT)); + OCIO_CHECK_NO_THROW(optOps.finalize()); + OCIO_CHECK_NO_THROW(optOps.optimize(AllBut(OCIO::OPTIMIZATION_IDENTITY_GAMMA))); - OCIO_REQUIRE_EQUAL(optOps.size(), 1); - OCIO_CHECK_EQUAL(optOps[0]->getInfo(), ""); + OCIO_REQUIRE_EQUAL(optOps.size(), 1); + OCIO_CHECK_EQUAL(optOps[0]->getInfo(), ""); + } + { + OCIO::OpRcPtrVec optOps = ops.clone(); + + // BASIC gammas are composed resulting in an identity, that get optimized as a range. + OCIO_CHECK_NO_THROW(optOps.finalize()); + OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT)); + + OCIO_REQUIRE_EQUAL(optOps.size(), 1); + OCIO_CHECK_EQUAL(optOps[0]->getInfo(), ""); + } + + // Now do the same test with MONCURVE rather than BASIC style. ops.clear(); - optOps.clear(); params1 = { 2., 0.5 }; params2 = { 2., 0.6 }; @@ -1075,7 +1133,7 @@ OCIO_ADD_TEST(OpOptimizers, gamma_comp_identity) OCIO_CHECK_NO_THROW(ops.finalize()); OCIO_CHECK_EQUAL(ops.size(), 2); - optOps = ops.clone(); + OCIO::OpRcPtrVec optOps = ops.clone(); // MONCURVE composition is not supported yet. OCIO_CHECK_NO_THROW(optOps.finalize()); diff --git a/tests/cpu/fileformats/FileFormatCTF_tests.cpp b/tests/cpu/fileformats/FileFormatCTF_tests.cpp index 0635c4a0cf..e4906fd40b 100644 --- a/tests/cpu/fileformats/FileFormatCTF_tests.cpp +++ b/tests/cpu/fileformats/FileFormatCTF_tests.cpp @@ -955,6 +955,41 @@ OCIO_ADD_TEST(FileFormatCTF, lut1d_inv) OCIO_CHECK_CLOSE(a2.getValues()[50], 1.0f, error); } +OCIO_ADD_TEST(FileFormatCTF, lut1d_inv_scaling) +{ + // Validate that the InverseLUT1D array values are scaled based on inBitDepth. + // (The previous example had inBitDepth=32f, so it does not validate that.) + + OCIO::LocalCachedFileRcPtr cachedFile; + const std::string ctfFile("lut1d_inverse_halfdom_slog_fclut.ctf"); + OCIO_CHECK_NO_THROW(cachedFile = LoadCLFFile(ctfFile)); + OCIO_REQUIRE_ASSERT((bool)cachedFile); + + const OCIO::ConstOpDataVec & opList = cachedFile->m_transform->getOps(); + OCIO_REQUIRE_EQUAL(opList.size(), 1); + + auto pLut = std::dynamic_pointer_cast(opList[0]); + OCIO_REQUIRE_ASSERT(pLut); + // For an InverseLUT1D, the file "out" depth is actually taken from inBitDepth. + OCIO_CHECK_EQUAL(pLut->getFileOutputBitDepth(), OCIO::BIT_DEPTH_UINT16); + OCIO_CHECK_EQUAL(pLut->getDirection(), OCIO::TRANSFORM_DIR_INVERSE); + + const OCIO::Array & a2 = pLut->getArray(); + OCIO_CHECK_EQUAL(a2.getNumColorComponents(), 1); + + OCIO_CHECK_EQUAL(a2.getLength(), 65536); + OCIO_CHECK_EQUAL(a2.getNumValues(), + a2.getLength()*a2.getMaxColorComponents()); + + const float error = 1e-6f; + OCIO_REQUIRE_EQUAL(a2.getValues().size(), a2.getNumValues()); + + // Input value 17830 scaled by 65535. + OCIO_CHECK_CLOSE(a2.getValues()[0], 0.27206836f, error); + // Input value 55070 scaled by 65535. + OCIO_CHECK_CLOSE(a2.getValues()[31743 * 3], 0.84031434f, error); +} + namespace { OCIO::LocalCachedFileRcPtr ParseString(const std::string & str) @@ -1056,7 +1091,9 @@ OCIO_ADD_TEST(FileFormatCTF, lut3d_inv) auto pLut = std::dynamic_pointer_cast(opList[0]); OCIO_REQUIRE_ASSERT(pLut); + // For an InverseLUT3D, the file "out" depth is set by the inBitDepth of the file. OCIO_CHECK_EQUAL(pLut->getFileOutputBitDepth(), OCIO::BIT_DEPTH_UINT12); + OCIO_CHECK_EQUAL(pLut->getInterpolation(), OCIO::INTERP_TETRAHEDRAL); OCIO_CHECK_EQUAL(pLut->getDirection(), OCIO::TRANSFORM_DIR_INVERSE); @@ -1067,6 +1104,7 @@ OCIO_ADD_TEST(FileFormatCTF, lut3d_inv) *array.getLength()*array.getMaxColorComponents()); OCIO_REQUIRE_EQUAL(array.getValues().size(), array.getNumValues()); + // Validate that the array was scaled by the inBitDepth of the file. OCIO_CHECK_EQUAL(array.getLength(), 17); OCIO_CHECK_CLOSE(array.getValues()[0], 25.0f / 4095.0f, 1e-8f); OCIO_CHECK_CLOSE(array.getValues()[1], 30.0f / 4095.0f, 1e-8f); @@ -7644,9 +7682,12 @@ OCIO_ADD_TEST(CTFTransform, lut1d_inverse_ctf) OCIO_CHECK_NO_THROW(WriteGroupCTF(group, outputTransform)); // Note the type of the node. + // + // For an InverseLUT1D, the scaling of array values is based on the inBitDepth. + // const std::string expected{ R"( - + 0 1 2 3 4 5 @@ -7812,9 +7853,12 @@ OCIO_ADD_TEST(CTFTransform, lut3d_inverse_ctf) OCIO_CHECK_NO_THROW(WriteGroupCTF(group, outputTransform)); // Note the type of the node. + // + // For an InverseLUT3D, the scaling of array values is based on the inBitDepth. + // const std::string expected{ R"( - + 0 1 2 3 4 5 @@ -7946,39 +7990,80 @@ OCIO_ADD_TEST(CTFTransform, bitdepth_ctf) auto range = OCIO::RangeTransform::Create(); range->setFileInputBitDepth(OCIO::BIT_DEPTH_F16); range->setFileOutputBitDepth(OCIO::BIT_DEPTH_UINT12); - range->setMinInValue(0.); - range->setMinOutValue(0.); + range->setMinInValue(0.1); + range->setMinOutValue(-0.1); + range->setMaxInValue(0.9); + range->setMaxOutValue(1.1); + + auto log = OCIO::LogTransform::Create(); + + auto invlut = OCIO::Lut1DTransform::Create(); + invlut->setDirection(OCIO::TRANSFORM_DIR_INVERSE); + invlut->setFileOutputBitDepth(OCIO::BIT_DEPTH_UINT16); + invlut->setLength(3); auto mat2 = OCIO::MatrixTransform::Create(); mat2->setFileInputBitDepth(OCIO::BIT_DEPTH_UINT8); mat2->setFileOutputBitDepth(OCIO::BIT_DEPTH_UINT10); - - auto log = OCIO::LogTransform::Create(); + mat2->setDirection(OCIO::TRANSFORM_DIR_INVERSE); OCIO::GroupTransformRcPtr group = OCIO::GroupTransform::Create(); group->getFormatMetadata().addAttribute(OCIO::METADATA_ID, "UID42"); - // First op keeps bit-depth + // Transforms are setup as follows: + // + // Matrix fileIn = 8i, fileOut = 10i + // Lut1D fileOut = 10i + // Exponent + // Range fileIn = 16f, fileOut = 12i + // Matrix fileIn = 8i, fileOut = 10i + // Log + // InvLut1D fileOut = 16i + // InvMatrix fileIn = 8i, fileOut = 10i + // InvLut1D fileOut = 16i + + // First op keeps its in & out bit-depth. + // group->appendTransform(mat); // Previous op out bit-depth used for in bit-depth. + // group->appendTransform(lut); // Previous op out bit-depth used for in bit-depth. // And next op (range) in bit-depth used for out bit-depth. + // group->appendTransform(exp); // In bit-depth preserved and has been used for out bit-depth of previous op. // Next op is a matrix, but current op is range, first op out bit-depth - // is preserved and used for next op in bit-depth. + // is preserved and overrides the next op's in in bit-depth. + // group->appendTransform(range); // Previous op out bit-depth used for in bit-depth. - group->appendTransform(mat2); + // + group->appendTransform(mat); - // Previous op out bit-depth used for in bit-depth. + // Previous op out bit-depth used for in bit-depth. Out depth is set by preference + // of the next op. + // group->appendTransform(log); + // Preferred in bit-depth is preserved. Out depth is set by the next op. + // + group->appendTransform(invlut); + + // Sets both its preferred in and out depth. Note that because the transform + // direction is inverse, the mapping of file depths is swapped. + // + group->appendTransform(mat2); + + // This time it doesn't get its preferred in depth, since the previous op has priority. + // The array values are scaled accordingly. + // + group->appendTransform(invlut); + std::ostringstream outputTransform; OCIO_CHECK_NO_THROW(WriteGroupCTF(group, outputTransform)); @@ -8002,8 +8087,10 @@ OCIO_ADD_TEST(CTFTransform, bitdepth_ctf) - 0 - 0 + 0.1 + 0.9 + -409.5 + 4504.5 @@ -8012,10 +8099,32 @@ OCIO_ADD_TEST(CTFTransform, bitdepth_ctf) 0 0 0.24981684981685 - + + + + 0 +32767.5 + 65535 + + + + + 0.249266862170088 0 0 + 0 0.249266862170088 0 + 0 0 0.249266862170088 + + + + + 0 +127.5 + 255 + + )" }; + OCIO_CHECK_EQUAL(expected.size(), outputTransform.str().size()); OCIO_CHECK_EQUAL(expected, outputTransform.str()); } diff --git a/tests/cpu/fileformats/FileFormatIridasCube_tests.cpp b/tests/cpu/fileformats/FileFormatIridasCube_tests.cpp index 13312bdb5d..b834ce10a7 100644 --- a/tests/cpu/fileformats/FileFormatIridasCube_tests.cpp +++ b/tests/cpu/fileformats/FileFormatIridasCube_tests.cpp @@ -161,6 +161,43 @@ OCIO_ADD_TEST(FileFormatIridasCube, read_failure) } } +OCIO_ADD_TEST(FileFormatIridasCube, whitespace_handling) +{ + const std::string SAMPLE = + "# comment\n" + "# comment with trailing space \n" + "# next up various forms of empty lines\n" + "\n" + " \n" + " \t \n" + "# whitespace before keywords or after data should be supported\n" + " LUT_3D_SIZE \t 2 \t\n" + "\t \tDOMAIN_MIN 0.25 0.5 0.75\n" + "\n" + "DOMAIN_MAX\t1.5\t2.5\t3.5\n" + + "0.0 0.0 0.0\n" + "# comments in between data should be ignored\n" + " 1.0 0.0 \t 0.0\n" + "0.0 1.0 0.0 \n" + " 1.0 1.0 0.0\n" + " \n" + "0.0 0.0 1.0\n" + "1.0 0.0 1.0\n" + "0.0 1.0 1.0\n" + "1.0 1.0 1.0\n" + " \n" + "\n"; + + OCIO::LocalCachedFileRcPtr file; + OCIO_CHECK_NO_THROW(file = ReadIridasCube(SAMPLE)); + OCIO_CHECK_EQUAL(file->domain_min[0], 0.25f); + OCIO_CHECK_EQUAL(file->domain_min[2], 0.75f); + OCIO_CHECK_EQUAL(file->domain_max[0], 1.5f); + OCIO_CHECK_EQUAL(file->domain_max[2], 3.5f); + OCIO_CHECK_EQUAL(file->lut3D->getArray().getLength(), 2); +} + OCIO_ADD_TEST(FileFormatIridasCube, no_shaper) { // check baker output diff --git a/tests/cpu/fileformats/xmlutils/XMLReaderUtils_tests.cpp b/tests/cpu/fileformats/xmlutils/XMLReaderUtils_tests.cpp index ad33e496ef..44aa7d59fb 100644 --- a/tests/cpu/fileformats/xmlutils/XMLReaderUtils_tests.cpp +++ b/tests/cpu/fileformats/xmlutils/XMLReaderUtils_tests.cpp @@ -46,12 +46,21 @@ OCIO_ADD_TEST(XMLReaderHelper, string_to_float_failure) const char str2[] = "12345"; const size_t len2 = std::strlen(str2); // All characters are parsed and this is more than the required length. - // The string to double function strtod does not stop at a given length, - // but we detect that strtod did read too many characters. - OCIO_CHECK_THROW_WHAT(OCIO::ParseNumber(str2, 0, len2 - 2, value), - OCIO::Exception, - "followed by unexpected characters"); - + // The string to double function might not stop at a given length, + // but we detect if it did read too many characters. + try + { + OCIO::ParseNumber(str2, 0, len2 - 2, value); + // At this point value should be 123 if underlying implementation + // properly stops at given length. + OCIO_CHECK_EQUAL(value, 123.0f); + } + catch (OCIO::Exception const& ex) + { + // If underlying implementation scans past the end, exception should be thrown. + const std::string what(ex.what()); + OCIO_CHECK_ASSERT(what.find("followed by unexpected characters") != std::string::npos); + } const char str3[] = "123XX"; const size_t len3 = std::strlen(str3); @@ -385,13 +394,6 @@ OCIO_ADD_TEST(XMLReaderHelper, parse_number) OCIO_CHECK_EQUAL(data, 1.0f); } - { - std::string buffer(" 123 "); - OCIO_CHECK_THROW_WHAT(OCIO::ParseNumber(buffer.c_str(), - 0, 3, data), - OCIO::Exception, - "followed by unexpected characters"); - } { std::string buffer("XY"); OCIO_CHECK_THROW_WHAT(OCIO::ParseNumber(buffer.c_str(), diff --git a/tests/cpu/transforms/BuiltinTransform_tests.cpp b/tests/cpu/transforms/BuiltinTransform_tests.cpp index ce0267c8a5..e46bd74fe6 100644 --- a/tests/cpu/transforms/BuiltinTransform_tests.cpp +++ b/tests/cpu/transforms/BuiltinTransform_tests.cpp @@ -626,6 +626,9 @@ AllValues UnitTestValues { "DISPLAY - CIE-XYZ-D65_to_DisplayP3", { 1.0e-6f, { 0.5f, 0.4f, 0.3f }, { 0.882580907776f, 0.581526360743f, 0.5606367050000f } } }, + { "DISPLAY - CIE-XYZ-D65_to_DisplayP3-HDR", + { 1.0e-6f, + { 0.5f, 0.4f, 0.3f }, { 0.882580907776f, 0.581526360743f, 0.5606367050000f } } }, { "CURVE - ST-2084_to_LINEAR", { 4.0e-5f, diff --git a/tests/cpu/transforms/DisplayViewTransform_tests.cpp b/tests/cpu/transforms/DisplayViewTransform_tests.cpp index 50ae88ec12..082ea3ee0e 100644 --- a/tests/cpu/transforms/DisplayViewTransform_tests.cpp +++ b/tests/cpu/transforms/DisplayViewTransform_tests.cpp @@ -1109,6 +1109,16 @@ ocio_profile_version: 2 dt->setDisplay("display"); dt->setView("view"); + // This results in the following conversion: + // displayCSIn to scene_ref offset= -0.15 0.15 0.15 0.05 + // scene_ref to display_ref offset= -0.2 -0.2 -0.4 -0 + // display_ref to displayCSProcess (look process_space) offset= -0.1 -0.1 -0.1 -0 + // apply look offset= 0.1 0.2 0.3 0 + // displayCSProcess to display_ref offset= 0.1 0.1 0.1 0 + // apply display_vt offset= -0.3 -0.1 -0.1 -0 + // display_ref to displayCSOut offset= -0.25 -0.15 -0.35 -0 + // Total transformation: offset= -0.8 -0.1 -0.4 0.05 + OCIO::ConstProcessorRcPtr proc; OCIO_CHECK_NO_THROW(proc = config->getProcessor(dt)); // Remove the no-ops, since they are useless here. diff --git a/tests/data/files/lut3d_arbitrary.csp b/tests/data/files/lut3d_arbitrary.csp new file mode 100644 index 0000000000..7babf7c08b --- /dev/null +++ b/tests/data/files/lut3d_arbitrary.csp @@ -0,0 +1,22 @@ +CSPLUTV100 +3D + +2 +0.0 1.0 +0.0 1.0 +2 +0.0 1.0 +0.0 1.0 +2 +0.0 1.0 +0.0 1.0 + +2 2 2 +0.100000 0.100000 0.100000 +1.100000 0.100000 0.100000 +0.100000 1.100000 0.100000 +1.100000 1.100000 0.100000 +0.100000 0.100000 1.100000 +1.100000 0.100000 1.100000 +0.100000 1.100000 1.100000 +1.100000 1.100000 1.100000 diff --git a/tests/data/files/lut3d_blu-only.csp b/tests/data/files/lut3d_blu-only.csp new file mode 100644 index 0000000000..e9f68bee8c --- /dev/null +++ b/tests/data/files/lut3d_blu-only.csp @@ -0,0 +1,22 @@ +CSPLUTV100 +3D + +2 +0.0 1.0 +0.0 1.0 +2 +0.0 1.0 +0.0 1.0 +2 +0.0 1.0 +0.0 1.0 + +2 2 2 +0.0 0.0 0.0 +0.0 0.0 0.0 +0.0 0.0 0.0 +0.0 0.0 0.0 +0.0 0.0 1.0 +0.0 0.0 1.0 +0.0 0.0 1.0 +0.0 0.0 1.0 diff --git a/tests/data/files/lut3d_grn-only.csp b/tests/data/files/lut3d_grn-only.csp new file mode 100644 index 0000000000..e62fa8fce6 --- /dev/null +++ b/tests/data/files/lut3d_grn-only.csp @@ -0,0 +1,22 @@ +CSPLUTV100 +3D + +2 +0.0 1.0 +0.0 1.0 +2 +0.0 1.0 +0.0 1.0 +2 +0.0 1.0 +0.0 1.0 + +2 2 2 +0.0 0.0 0.0 +0.0 0.0 0.0 +0.0 0.0 0.0 +0.0 0.0 0.0 +0.0 1.0 0.0 +0.0 1.0 0.0 +0.0 1.0 0.0 +0.0 1.0 0.0 diff --git a/tests/data/files/lut3d_red-only.csp b/tests/data/files/lut3d_red-only.csp new file mode 100644 index 0000000000..7a08c38d81 --- /dev/null +++ b/tests/data/files/lut3d_red-only.csp @@ -0,0 +1,22 @@ +CSPLUTV100 +3D + +2 +0.0 1.0 +0.0 1.0 +2 +0.0 1.0 +0.0 1.0 +2 +0.0 1.0 +0.0 1.0 + +2 2 2 +0.0 0.0 0.0 +0.0 0.0 0.0 +0.0 0.0 0.0 +0.0 0.0 0.0 +1.0 0.0 0.0 +1.0 0.0 0.0 +1.0 0.0 0.0 +1.0 0.0 0.0 diff --git a/tests/gpu/CDLOp_test.cpp b/tests/gpu/CDLOp_test.cpp index 9f32d0d42a..ddf1b43eeb 100644 --- a/tests/gpu/CDLOp_test.cpp +++ b/tests/gpu/CDLOp_test.cpp @@ -9,7 +9,6 @@ #include #include "GPUUnitTest.h" -#include "GPUHelpers.h" namespace OCIO = OCIO_NAMESPACE; diff --git a/tests/gpu/CMakeLists.txt b/tests/gpu/CMakeLists.txt index 4e16243bfd..2c56f2948b 100644 --- a/tests/gpu/CMakeLists.txt +++ b/tests/gpu/CMakeLists.txt @@ -12,7 +12,6 @@ set(SOURCES ECOp_test.cpp FixedFunctionOp_test.cpp GammaOp_test.cpp - GPUHelpers.cpp GPUUnitTest.cpp GradingPrimaryOp_test.cpp GradingRGBCurveOp_test.cpp diff --git a/tests/gpu/GPUHelpers.cpp b/tests/gpu/GPUHelpers.cpp deleted file mode 100644 index 317e772461..0000000000 --- a/tests/gpu/GPUHelpers.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright Contributors to the OpenColorIO Project. - - -#include -#include - -#if !defined(_WIN32) -#include -#include -#include -#endif - -#include - -#include "GPUHelpers.h" - -namespace OCIO = OCIO_NAMESPACE; - - -// TODO: Make OCIO::Platform::CreateTempFilename() public so it could be used here. - -std::string createTempFile(const std::string& fileExt, const std::string& fileContent) -{ - // Note: because of security issue, tmpnam could not be used - - std::string filename; - -#ifdef _WIN32 - - char tmpFilename[L_tmpnam]; - if(tmpnam_s(tmpFilename)) - { - throw OCIO::Exception("Could not create a temporary file"); - } - - // Note that when a file name is pre-pended with a backslash and no path information, such as \fname21, this - // indicates that the name is valid for the current working directory. - filename = tmpFilename[0] == '\\' ? tmpFilename + 1 : tmpFilename; - filename += fileExt; - -#else - - std::stringstream ss; - ss << "/tmp/ocio"; - ss << std::rand(); - ss << fileExt; - - filename = ss.str(); - -#endif - - std::ofstream ofs(filename.c_str(), std::ios_base::out); - ofs << fileContent; - ofs.close(); - - return filename; -} diff --git a/tests/gpu/GPUHelpers.h b/tests/gpu/GPUHelpers.h deleted file mode 100644 index e49be0e1d3..0000000000 --- a/tests/gpu/GPUHelpers.h +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: BSD-3-Clause -// Copyright Contributors to the OpenColorIO Project. - - -#ifndef OPENCOLORIO_GPU_HELPERS_H -#define OPENCOLORIO_GPU_HELPERS_H - - -#include - -// FIXME: Duplicate function implemented in `src/OpenColorIO/Platform.h and cpp`. -// Implement a function or class for temporary file creation useable by all tests. -std::string createTempFile(const std::string& fileExt, const std::string& fileContent); - - -#endif \ No newline at end of file diff --git a/tests/gpu/Lut1DOp_test.cpp b/tests/gpu/Lut1DOp_test.cpp index 91d13446bb..cb76697619 100644 --- a/tests/gpu/Lut1DOp_test.cpp +++ b/tests/gpu/Lut1DOp_test.cpp @@ -9,7 +9,6 @@ #include #include "GPUUnitTest.h" -#include "GPUHelpers.h" namespace OCIO = OCIO_NAMESPACE; diff --git a/tests/gpu/Lut3DOp_test.cpp b/tests/gpu/Lut3DOp_test.cpp index d5fc45a0b6..d1ee3aa3de 100644 --- a/tests/gpu/Lut3DOp_test.cpp +++ b/tests/gpu/Lut3DOp_test.cpp @@ -7,47 +7,43 @@ #include -#include "GPUHelpers.h" #include "GPUUnitTest.h" namespace OCIO = OCIO_NAMESPACE; +#ifndef OCIO_UNIT_TEST_FILES_DIR +#error Expecting OCIO_UNIT_TEST_FILES_DIR to be defined for tests. Check relevant CMakeLists.txt +#endif + + +// For explanation, refer to https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html +#define _STR(x) #x +#define STR(x) _STR(x) + +static const std::string ocioTestFilesDir(STR(OCIO_UNIT_TEST_FILES_DIR)); + + +namespace +{ +OCIO::FileTransformRcPtr GetFileTransform(const std::string & filename) +{ + const std::string filepath(ocioTestFilesDir + std::string("/") + filename); + + OCIO::FileTransformRcPtr file = OCIO::FileTransform::Create(); + file->setSrc(filepath.c_str()); + + return file; +} +} + OCIO_ADD_GPU_TEST(Lut3DOp, red_only_using_CSP_file_legacy_shader) { // Any other 3D LUT file format would have been good also. - std::ostringstream content; - content << "CSPLUTV100" << "\n"; - content << "3D" << "\n"; - content << "" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "" << "\n"; - content << "2 2 2" << "\n"; - content << "0.0 0.0 0.0" << "\n"; - content << "0.0 0.0 0.0" << "\n"; - content << "0.0 0.0 0.0" << "\n"; - content << "0.0 0.0 0.0" << "\n"; - content << "1.0 0.0 0.0" << "\n"; - content << "1.0 0.0 0.0" << "\n"; - content << "1.0 0.0 0.0" << "\n"; - content << "1.0 0.0 0.0" << "\n"; - - - const std::string filename = createTempFile(".csp", content.str()); - // Create the transform & set the unit test - OCIO::FileTransformRcPtr file = OCIO::FileTransform::Create(); - file->setSrc(filename.c_str()); + OCIO::FileTransformRcPtr file = GetFileTransform("lut3d_red-only.csp"); file->setInterpolation(OCIO::INTERP_LINEAR); test.setProcessor(file); @@ -60,37 +56,9 @@ OCIO_ADD_GPU_TEST(Lut3DOp, green_only_using_CSP_file_legacy_shader) { // Any other 3D LUT file format would have been good also. - std::ostringstream content; - content << "CSPLUTV100" << "\n"; - content << "3D" << "\n"; - content << "" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "" << "\n"; - content << "2 2 2" << "\n"; - content << "0.0 0.0 0.0" << "\n"; - content << "0.0 0.0 0.0" << "\n"; - content << "0.0 0.0 0.0" << "\n"; - content << "0.0 0.0 0.0" << "\n"; - content << "0.0 1.0 0.0" << "\n"; - content << "0.0 1.0 0.0" << "\n"; - content << "0.0 1.0 0.0" << "\n"; - content << "0.0 1.0 0.0" << "\n"; - - - const std::string filename = createTempFile(".csp", content.str()); - // Create the transform & set the unit test - OCIO::FileTransformRcPtr file = OCIO::FileTransform::Create(); - file->setSrc(filename.c_str()); + OCIO::FileTransformRcPtr file = GetFileTransform("lut3d_grn-only.csp"); file->setInterpolation(OCIO::INTERP_LINEAR); test.setProcessor(file); @@ -103,37 +71,10 @@ OCIO_ADD_GPU_TEST(Lut3DOp, blue_only_using_CSP_file_legacy_shader) { // Any other 3D LUT file format would have been good also. - std::ostringstream content; - content << "CSPLUTV100" << "\n"; - content << "3D" << "\n"; - content << "" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "" << "\n"; - content << "2 2 2" << "\n"; - content << "0.0 0.0 0.0" << "\n"; - content << "0.0 0.0 0.0" << "\n"; - content << "0.0 0.0 0.0" << "\n"; - content << "0.0 0.0 0.0" << "\n"; - content << "0.0 0.0 1.0" << "\n"; - content << "0.0 0.0 1.0" << "\n"; - content << "0.0 0.0 1.0" << "\n"; - content << "0.0 0.0 1.0" << "\n"; - - - const std::string filename = createTempFile(".csp", content.str()); // Create the transform & set the unit test - OCIO::FileTransformRcPtr file = OCIO::FileTransform::Create(); - file->setSrc(filename.c_str()); + OCIO::FileTransformRcPtr file = GetFileTransform("lut3d_blu-only.csp"); file->setInterpolation(OCIO::INTERP_LINEAR); test.setProcessor(file); @@ -147,37 +88,9 @@ OCIO_ADD_GPU_TEST(Lut3DOp, arbitrary_using_CSP_file_legacy_shader) { // Any other 3D LUT file format would have been good also. - std::ostringstream content; - content << "CSPLUTV100" << "\n"; - content << "3D" << "\n"; - content << "" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "" << "\n"; - content << "2 2 2" << "\n"; - content << "0.100000 0.100000 0.100000" << "\n"; - content << "1.100000 0.100000 0.100000" << "\n"; - content << "0.100000 1.100000 0.100000" << "\n"; - content << "1.100000 1.100000 0.100000" << "\n"; - content << "0.100000 0.100000 1.100000" << "\n"; - content << "1.100000 0.100000 1.100000" << "\n"; - content << "0.100000 1.100000 1.100000" << "\n"; - content << "1.100000 1.100000 1.100000" << "\n"; - - - const std::string filename = createTempFile(".csp", content.str()); - // Create the transform & set the unit test - OCIO::FileTransformRcPtr file = OCIO::FileTransform::Create(); - file->setSrc(filename.c_str()); + OCIO::FileTransformRcPtr file = GetFileTransform("lut3d_arbitrary.csp"); file->setInterpolation(OCIO::INTERP_LINEAR); test.setProcessor(file); @@ -189,37 +102,9 @@ OCIO_ADD_GPU_TEST(Lut3DOp, arbitrary_using_CSP_file_legacy_shader) OCIO_ADD_GPU_TEST(Lut3DOp, arbitrary_using_CSP_file) { - std::ostringstream content; - content << "CSPLUTV100" << "\n"; - content << "3D" << "\n"; - content << "" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "2" << "\n"; - content << "0.0 1.0" << "\n"; - content << "0.0 1.0" << "\n"; - content << "" << "\n"; - content << "2 2 2" << "\n"; - content << "0.100000 0.100000 0.100000" << "\n"; - content << "1.100000 0.100000 0.100000" << "\n"; - content << "0.100000 1.100000 0.100000" << "\n"; - content << "1.100000 1.100000 0.100000" << "\n"; - content << "0.100000 0.100000 1.100000" << "\n"; - content << "1.100000 0.100000 1.100000" << "\n"; - content << "0.100000 1.100000 1.100000" << "\n"; - content << "1.100000 1.100000 1.100000" << "\n"; - - - const std::string filename = createTempFile(".csp", content.str()); - // Create the transform & set the unit test - OCIO::FileTransformRcPtr file = OCIO::FileTransform::Create(); - file->setSrc(filename.c_str()); + OCIO::FileTransformRcPtr file = GetFileTransform("lut3d_arbitrary.csp"); file->setInterpolation(OCIO::INTERP_LINEAR); test.setProcessor(file); @@ -232,33 +117,6 @@ OCIO_ADD_GPU_TEST(Lut3DOp, arbitrary_using_CSP_file) } - -#ifndef OCIO_UNIT_TEST_FILES_DIR -#error Expecting OCIO_UNIT_TEST_FILES_DIR to be defined for tests. Check relevant CMakeLists.txt -#endif - - -// For explanation, refer to https://gcc.gnu.org/onlinedocs/cpp/Stringizing.html -#define _STR(x) #x -#define STR(x) _STR(x) - -static const std::string ocioTestFilesDir(STR(OCIO_UNIT_TEST_FILES_DIR)); - - -namespace -{ -OCIO::FileTransformRcPtr GetFileTransform(const std::string & filename) -{ - const std::string filepath(ocioTestFilesDir + std::string("/") + filename); - - OCIO::FileTransformRcPtr file = OCIO::FileTransform::Create(); - file->setSrc(filepath.c_str()); - - return file; -} -} - - OCIO_ADD_GPU_TEST(Lut3DOp, 3dlut_file_legacy_shader) { OCIO::FileTransformRcPtr file = GetFileTransform("lut3d_1.spi3d"); @@ -389,4 +247,3 @@ OCIO_ADD_GPU_TEST(Lut3DOp, 3dlut_biggest_supported) test.setErrorThreshold(1e-4f); } - diff --git a/tests/gpu/RangeOp_test.cpp b/tests/gpu/RangeOp_test.cpp index c94906cfe9..3ae2c90012 100644 --- a/tests/gpu/RangeOp_test.cpp +++ b/tests/gpu/RangeOp_test.cpp @@ -7,7 +7,6 @@ #include -#include "GPUHelpers.h" #include "GPUUnitTest.h" namespace OCIO = OCIO_NAMESPACE; diff --git a/tests/utils/CMakeLists.txt b/tests/utils/CMakeLists.txt index 242cad4f0d..bf1858ff92 100644 --- a/tests/utils/CMakeLists.txt +++ b/tests/utils/CMakeLists.txt @@ -5,6 +5,7 @@ include(ExternalProject) set(SOURCES UnitTestMain.cpp + NumberUtils_tests.cpp StringUtils_tests.cpp ) diff --git a/tests/utils/NumberUtils_tests.cpp b/tests/utils/NumberUtils_tests.cpp new file mode 100644 index 0000000000..ba03cd9a7f --- /dev/null +++ b/tests/utils/NumberUtils_tests.cpp @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + + +#include "testutils/UnitTest.h" +#include "utils/NumberUtils.h" + +#include + +namespace OCIO = OCIO_NAMESPACE; + +OCIO_ADD_TEST(NumberUtils, from_chars_float) +{ +#define TEST_FROM_CHARS(text) \ + str = text; \ + res = OCIO::NumberUtils::from_chars(str.data(), str.data() + str.size(), val); \ + OCIO_CHECK_ASSERT(res.ec == std::errc()); \ + OCIO_CHECK_ASSERT(res.ptr == str.data() + str.size()) + + std::string str; + float val = 0.0f; + OCIO::NumberUtils::from_chars_result res; + + // regular numbers + TEST_FROM_CHARS("-7"); OCIO_CHECK_EQUAL(val, -7.0f); + TEST_FROM_CHARS("1.5"); OCIO_CHECK_EQUAL(val, 1.5f); + TEST_FROM_CHARS("-17.25"); OCIO_CHECK_EQUAL(val, -17.25f); + TEST_FROM_CHARS("-.75"); OCIO_CHECK_EQUAL(val, -.75f); + TEST_FROM_CHARS("11."); OCIO_CHECK_EQUAL(val, 11.0f); + // exponent notation + TEST_FROM_CHARS("1e3"); OCIO_CHECK_EQUAL(val, 1000.0f); + TEST_FROM_CHARS("1e+2"); OCIO_CHECK_EQUAL(val, 100.0f); + TEST_FROM_CHARS("50e-2"); OCIO_CHECK_EQUAL(val, 0.5f); + TEST_FROM_CHARS("-1.5e2"); OCIO_CHECK_EQUAL(val, -150.0f); + // whitespace/prefix handling + TEST_FROM_CHARS("+57.125"); OCIO_CHECK_EQUAL(val, +57.125f); + TEST_FROM_CHARS(" \t 123.5"); OCIO_CHECK_EQUAL(val, 123.5f); + // special values + TEST_FROM_CHARS("-infinity"); OCIO_CHECK_EQUAL(val, -std::numeric_limits::infinity()); + TEST_FROM_CHARS("nan"); OCIO_CHECK_ASSERT(std::isnan(val)); + // hex format should be parsed + TEST_FROM_CHARS("0x42"); OCIO_CHECK_EQUAL(val, 66.0f); + TEST_FROM_CHARS("0x42ab.c"); OCIO_CHECK_EQUAL(val, 17067.75f); + + // valid numbers with trailing non-number chars should stop there + str = "-7.5ab"; + res = OCIO::NumberUtils::from_chars(str.data(), str.data() + str.size(), val); + OCIO_CHECK_ASSERT(res.ptr == str.data() + 4); + OCIO_CHECK_EQUAL(val, -7.5f); + str = "infinitya"; + res = OCIO::NumberUtils::from_chars(str.data(), str.data() + str.size(), val); + OCIO_CHECK_ASSERT(res.ptr == str.data() + 8); + OCIO_CHECK_EQUAL(val, std::numeric_limits::infinity()); + str = "0x18g"; + res = OCIO::NumberUtils::from_chars(str.data(), str.data() + str.size(), val); + OCIO_CHECK_ASSERT(res.ptr == str.data() + 4); + OCIO_CHECK_EQUAL(val, 24.0f); + +#undef TEST_FROM_CHARS +} + +OCIO_ADD_TEST(NumberUtils, from_chars_float_failures) +{ +#define TEST_FROM_CHARS(text) \ + str = text; \ + res = OCIO::NumberUtils::from_chars(str.data(), str.data() + str.size(), val); \ + OCIO_CHECK_EQUAL(val, 7.5f); \ + OCIO_CHECK_ASSERT(res.ec == std::errc::invalid_argument) + + std::string str; + float val = 7.5f; + OCIO::NumberUtils::from_chars_result res; + + TEST_FROM_CHARS(""); + TEST_FROM_CHARS("ab"); + TEST_FROM_CHARS(" "); + TEST_FROM_CHARS("---"); + TEST_FROM_CHARS("e3"); + TEST_FROM_CHARS("_x"); + TEST_FROM_CHARS("+."); + +#undef TEST_FROM_CHARS +}