diff --git a/.github/workflows/ci_workflow.yml b/.github/workflows/ci_workflow.yml index 41c7afb08a..68d02377ae 100644 --- a/.github/workflows/ci_workflow.yml +++ b/.github/workflows/ci_workflow.yml @@ -73,6 +73,7 @@ jobs: build-openfx: 'ON' use-simd: 'ON' use-oiio: 'ON' + aswf-ocio-version: 2.5.2 cxx-standard: 20 cxx-compiler: clang++ cc-compiler: clang @@ -115,6 +116,7 @@ jobs: build-openfx: 'ON' use-simd: 'ON' use-oiio: 'ON' + aswf-ocio-version: 2.4.2 cxx-standard: 20 cxx-compiler: clang++ cc-compiler: clang @@ -157,6 +159,7 @@ jobs: build-openfx: 'ON' use-simd: 'ON' use-oiio: 'ON' + aswf-ocio-version: 2.3.2 cxx-standard: 20 cxx-compiler: clang++ cc-compiler: clang @@ -199,12 +202,12 @@ jobs: build-openfx: 'ON' use-simd: 'ON' use-oiio: 'ON' + aswf-ocio-version: 2.2.1 cxx-standard: 20 cxx-compiler: clang++ cc-compiler: clang compiler-desc: Clang vfx-cy: 2023 - container-tag: 2023.2 install-ext-packages: MISSING - build: 2 build-type: Release @@ -218,7 +221,6 @@ jobs: cc-compiler: gcc compiler-desc: GCC vfx-cy: 2023 - container-tag: 2023.2 install-ext-packages: ALL - build: 1 build-type: Release @@ -232,7 +234,6 @@ jobs: cc-compiler: gcc compiler-desc: GCC vfx-cy: 2023 - container-tag: 2023.2 install-ext-packages: ALL env: CXX: ${{ matrix.cxx-compiler }} @@ -245,6 +246,13 @@ jobs: if: matrix.build-docs == 'ON' - name: Install tests env run: share/ci/scripts/linux/dnf/install_tests_env.sh + - name: Install ASWF OpenColorIO for OpenImageIO + # The container's prebuilt OpenImageIO is linked against a specific + # OpenColorIO release that the container does not ship (it's built + # from source here instead). Fetch that release from the ASWF Conan + # remote so OpenImageIO's dependency resolves at link/run time. + if: matrix.use-oiio == 'ON' + run: share/ci/scripts/linux/dnf/install_aswf_opencolorio.sh ${{ matrix.aswf-ocio-version }} ${{ matrix.vfx-cy }} - name: Create build directories run: | mkdir _install @@ -316,7 +324,7 @@ jobs: working-directory: _build/tests/cmake-consumer-dist # --------------------------------------------------------------------------- - # macOS + # macOS Intel # --------------------------------------------------------------------------- macos: @@ -497,12 +505,19 @@ jobs: if: matrix.build-docs == 'ON' - name: Install tests env run: share/ci/scripts/macos/install_tests_env.sh + - name: Install OIIO + if: matrix.use-oiio == 'ON' + run: time share/ci/scripts/macos/install_oiio.sh - name: Create build directories run: | mkdir _install mkdir _build - name: Configure run: | + OIIO_CMAKE_ARGS=() + if [ "${{ matrix.use-oiio }}" = "ON" ]; then + OIIO_CMAKE_ARGS+=(-DOpenImageIO_ROOT=$(brew --prefix openimageio)) + fi cmake ../. \ -DCMAKE_INSTALL_PREFIX=../_install \ -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ @@ -516,7 +531,8 @@ jobs: -DOCIO_INSTALL_EXT_PACKAGES=ALL \ -DOCIO_WARNING_AS_ERROR=ON \ -DPython_EXECUTABLE=$(which python) \ - -DCMAKE_OSX_ARCHITECTURES="${{ matrix.arch-type }}" + -DCMAKE_OSX_ARCHITECTURES="${{ matrix.arch-type }}" \ + "${OIIO_CMAKE_ARGS[@]}" working-directory: _build - name: Build run: | @@ -602,7 +618,8 @@ jobs: build-docs: 'OFF' build-openfx: 'ON' use-simd: 'OFF' - use-oiio: 'ON' + # NB: OIIO is not currently set up for Windows CI builds + use-oiio: 'OFF' cxx-standard: 20 python-version: '3.13' - build: 3 diff --git a/.github/workflows/dependencies_latest.yml b/.github/workflows/dependencies_latest.yml index c10131e0d3..5757377159 100644 --- a/.github/workflows/dependencies_latest.yml +++ b/.github/workflows/dependencies_latest.yml @@ -308,6 +308,7 @@ jobs: run: | vcpkg install zlib:x64-windows vcpkg install tiff:x64-windows + vcpkg install directx-headers:x64-windows shell: bash - name: Install fixed ext package versions # Minizip-ng depends on ZLIB. ZLIB must be installed first. diff --git a/.readthedocs.yml b/.readthedocs.yml index 26c5cb2915..43fad1ab9b 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -7,9 +7,9 @@ version: 2 build: - os: ubuntu-20.04 + os: ubuntu-lts-latest tools: - python: "3.11" + python: "3.14" sphinx: configuration: docs/conf.py diff --git a/share/ci/scripts/linux/dnf/install_aswf_opencolorio.sh b/share/ci/scripts/linux/dnf/install_aswf_opencolorio.sh new file mode 100755 index 0000000000..7a0d8bb19e --- /dev/null +++ b/share/ci/scripts/linux/dnf/install_aswf_opencolorio.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: BSD-3-Clause +# Copyright Contributors to the OpenColorIO Project. +# +# The aswf/ci-ocio container images intentionally do not ship OpenColorIO, +# since the whole point of the image is to build OpenColorIO from source. +# However, the container's prebuilt OpenImageIO is dynamically linked +# against the specific OpenColorIO release used by the corresponding +# aswf-docker VFX Reference Platform year, and that library is not present +# in the image. Fetch the matching prebuilt OpenColorIO shared library from +# the ASWF Conan remote and stage it on the linker search path, so apps +# built against OCIO_USE_OIIO_FOR_APPS=ON can resolve OpenImageIO's +# transitive dependency at link and run time. +# +# Usage: install_aswf_opencolorio.sh + +set -ex + +OCIO_VERSION="$1" +VFX_YEAR="$2" +CONAN_REF="opencolorio/${OCIO_VERSION}@aswf/vfx${VFX_YEAR}" + +conan remote add aswf https://linuxfoundation.jfrog.io/artifactory/api/conan/aswf-conan + +PKG_ID=$(conan list "${CONAN_REF}:*" -r aswf --format=json 2>/dev/null \ + | jq -r --arg ref "${CONAN_REF}" '.aswf[$ref].revisions | to_entries[0].value.packages | keys[0]') + +conan download "${CONAN_REF}:${PKG_ID}" -r aswf + +PKG_PATH=$(conan cache path "${CONAN_REF}:${PKG_ID}") + +cp -P "${PKG_PATH}"/lib/libOpenColorIO.so* /usr/local/lib/ +ldconfig diff --git a/share/ci/scripts/macos/install_oiio.sh b/share/ci/scripts/macos/install_oiio.sh index 358aac6614..f5be573081 100755 --- a/share/ci/scripts/macos/install_oiio.sh +++ b/share/ci/scripts/macos/install_oiio.sh @@ -4,10 +4,9 @@ set -ex -OIIO_VERSION="$1" - -if [ "$OIIO_VERSION" = "latest" ]; then - brew install openimageio -else - brew install openimageio@${OIIO_VERSION} -fi +# Homebrew does not publish versioned openimageio@X formulae (unlike e.g. python@X), so a +# specific version cannot be pinned here without installing from an old homebrew-core formula +# commit, which risks building OIIO and its whole dependency tree from source if no bottle +# exists for that commit on the runner's current macOS/Xcode. Always install whatever the +# current bottle is instead. +brew install openimageio diff --git a/share/cmake/modules/FindDirectX-Headers.cmake b/share/cmake/modules/FindDirectX-Headers.cmake index 99f2788d4e..8774af4246 100644 --- a/share/cmake/modules/FindDirectX-Headers.cmake +++ b/share/cmake/modules/FindDirectX-Headers.cmake @@ -33,6 +33,10 @@ if(NOT OCIO_INSTALL_EXT_PACKAGES STREQUAL ALL) if(directx-headers_VERSION) set(DirectX-Headers_VERSION ${directx-headers_VERSION}) endif() + if(TARGET Microsoft::DirectX-Headers AND NOT DirectX-Headers_INCLUDE_DIR) + get_target_property(DirectX-Headers_INCLUDE_DIR + Microsoft::DirectX-Headers INTERFACE_INCLUDE_DIRECTORIES) + endif() else() # Fall back to locating the public header directly (e.g. when the # headers were installed without the CMake config, or are provided diff --git a/share/cmake/modules/FindExtPackages.cmake b/share/cmake/modules/FindExtPackages.cmake index 9bf17cf84e..df0d7af486 100644 --- a/share/cmake/modules/FindExtPackages.cmake +++ b/share/cmake/modules/FindExtPackages.cmake @@ -226,7 +226,12 @@ endif() if(OCIO_BUILD_APPS) - if(OCIO_USE_OIIO_FOR_APPS AND OpenImageIO_FOUND AND TARGET OpenImageIO::OpenImageIO) + if(OCIO_USE_OIIO_FOR_APPS) + if(NOT (OpenImageIO_FOUND AND TARGET OpenImageIO::OpenImageIO)) + message(FATAL_ERROR "OCIO_USE_OIIO_FOR_APPS is ON but OpenImageIO was not found. " + "Either install OpenImageIO or turn OCIO_USE_OIIO_FOR_APPS off " + "to build ociolutimage, ocioconvert and ociodisplay against OpenEXR instead.") + endif() if (USE_MSVC AND OCIO_IMAGE_BACKEND STREQUAL "OpenImageIO") # Temporary until fixed in OpenImageIO: Mute some warnings from OpenImageIO farmhash.h # C4267 (level 3) 'var' : conversion from 'size_t' to 'type', possible loss of data diff --git a/src/OpenColorIO/CPUInfo.cpp b/src/OpenColorIO/CPUInfo.cpp index edf341792b..3a6cd12f73 100644 --- a/src/OpenColorIO/CPUInfo.cpp +++ b/src/OpenColorIO/CPUInfo.cpp @@ -181,6 +181,18 @@ CPUInfo::CPUInfo() { cpuid(0x80000002 + index, (int *)(name + 16*index)); } + + // AMD erratum #1485 (Zen4, e.g. EPYC 8004/9004 and Ryzen 7000 series): when SMT is + // enabled and STIBP is not, affected CPUs can corrupt their own instruction stream + // during speculative execution, raising a spurious illegal-instruction fault on + // otherwise-correct code. There is no cheap, portable, unprivileged way to check the + // actual STIBP enablement state from here, and the documented Zen4 model numbers are + // scattered/non-contiguous, so this only blocks the exact SKU seen to hit this in + // practice rather than guessing at a broader family/model range. + if (!strncmp(vendor, "AuthenticAMD", 12) && strstr(name, "EPYC 9V45")) + { + flags &= ~X86_CPU_FLAG_AVX512; + } } #elif defined(__aarch64__) || defined(_M_ARM64) // ARM 64-bit processor (multiple platforms) diff --git a/tests/cpu/CMakeLists.txt b/tests/cpu/CMakeLists.txt index 2dab084f3e..f7e7468ab4 100755 --- a/tests/cpu/CMakeLists.txt +++ b/tests/cpu/CMakeLists.txt @@ -130,6 +130,11 @@ function(add_ocio_test NAME SOURCES TESTS PRIVATE_INCLUDES) if(OCIO_USE_AVX512) add_ocio_test_variant(${TEST_NAME}_avx512 ${TEST_BINARY} --avx512) + # Runs in its own process, independent of the full suite above, so it still + # executes (and reports its own result) even if some other AVX512 test crashes + # the ${TEST_NAME}_avx512 process before reaching it. + add_ocio_test_variant(${TEST_NAME}_avx512_diag ${TEST_BINARY} --avx512 + --run_only "Lut3DRenderer/avx512_tetrahedral_bounds") endif() else() add_ocio_test_variant(${TEST_NAME} ${TEST_BINARY}) diff --git a/tests/cpu/ops/lut3d/Lut3DOpCPU_tests.cpp b/tests/cpu/ops/lut3d/Lut3DOpCPU_tests.cpp index f7121b31ad..60bb57fb00 100644 --- a/tests/cpu/ops/lut3d/Lut3DOpCPU_tests.cpp +++ b/tests/cpu/ops/lut3d/Lut3DOpCPU_tests.cpp @@ -55,3 +55,95 @@ OCIO_ADD_TEST(Lut3DRenderer, nan_tetra_test) Lut3DRendererNaNTest(OCIO::INTERP_TETRAHEDRAL); } +#if OCIO_USE_AVX512 && defined(_WIN32) + +#include "ops/lut3d/Lut3DOpCPU_AVX512.h" +#include +#include + +namespace +{ +// Places a buffer so its last byte is immediately followed by a PAGE_NOACCESS page, so +// any read or write past the requested size raises an access violation right away +// instead of only sometimes, depending on heap layout. +class GuardedBuffer +{ +public: + explicit GuardedBuffer(size_t numFloats) + { + const size_t sizeBytes = numFloats * sizeof(float); + const size_t pageSize = 4096; + const size_t dataPages = (sizeBytes + pageSize - 1) / pageSize; + + m_base = static_cast(VirtualAlloc(nullptr, (dataPages + 1) * pageSize, + MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE)); + OCIO_REQUIRE_ASSERT(m_base); + + DWORD oldProtect = 0; + const BOOL ok = VirtualProtect(m_base + dataPages * pageSize, pageSize, + PAGE_NOACCESS, &oldProtect); + OCIO_REQUIRE_ASSERT(ok); + + m_ptr = reinterpret_cast(m_base + dataPages * pageSize - sizeBytes); + memset(m_ptr, 0, sizeBytes); + } + + ~GuardedBuffer() + { + VirtualFree(m_base, 0, MEM_RELEASE); + } + + GuardedBuffer(const GuardedBuffer &) = delete; + GuardedBuffer & operator=(const GuardedBuffer &) = delete; + + float * get() { return m_ptr; } + +private: + uint8_t * m_base = nullptr; + float * m_ptr = nullptr; +}; +} // anonymous namespace + +// Diagnostic test for the "illegal instruction" crash seen in +// OpOptimizers/invlut_pair_identities on the windows-2025-vs2026 CI runner (not +// reproduced on other Windows configurations so far). Exercises the AVX512 tetrahedral +// LUT3D interpolation directly, sweeping pixel counts across the 16-lane boundary (to +// separate the masked "remainder" path from the main loop) and both a tiny grid (2, +// matching tests/data/files/lut_inv_pairs.ctf) and a normal-sized one (32), with every +// buffer (LUT, source, destination) placed against a guard page. Any out-of-bounds +// access, regardless of mechanism, will raise a clean, immediate access violation +// naming the exact (dim, numPixels) combination instead of an unexplained crash. +OCIO_ADD_TEST(Lut3DRenderer, avx512_tetrahedral_bounds) +{ + if (!OCIO::CPUInfo::instance().hasAVX512()) throw SkipException(); + + for (int dim : { 2, 32 }) + { + const int lutFloats = dim * dim * dim * 4; + GuardedBuffer lut(lutFloats); + float * lutPtr = lut.get(); + for (int i = 0; i < lutFloats; ++i) + { + lutPtr[i] = static_cast(i % 7) / 7.0f; + } + + for (int numPixels = 1; numPixels <= 33; ++numPixels) + { + std::cerr << "avx512_tetrahedral_bounds: dim=" << dim + << " numPixels=" << numPixels << std::endl; + + GuardedBuffer src(numPixels * 4); + GuardedBuffer dst(numPixels * 4); + float * srcPtr = src.get(); + for (int i = 0; i < numPixels * 4; ++i) + { + srcPtr[i] = static_cast(i % 5) / 5.0f; + } + + OCIO::applyTetrahedralAVX512(lut.get(), dim, src.get(), dst.get(), numPixels); + } + } +} + +#endif // OCIO_USE_AVX512 && defined(_WIN32) +