From c0cf753eac6b13573668c9665acbc58755247f3a Mon Sep 17 00:00:00 2001 From: scott Date: Sun, 29 Mar 2015 08:52:53 -0700 Subject: [PATCH 1/5] Silenced a few warnings about non-initialized variables. --- src/libOpenImageIO/imagebufalgo_draw.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libOpenImageIO/imagebufalgo_draw.cpp b/src/libOpenImageIO/imagebufalgo_draw.cpp index 08f4c8085d..1b7b1734f3 100644 --- a/src/libOpenImageIO/imagebufalgo_draw.cpp +++ b/src/libOpenImageIO/imagebufalgo_draw.cpp @@ -262,7 +262,7 @@ hashrand (int x, int y, int z, int c, int seed) // Return a hash-based normal-distributed pseudorandom value. -// We use the Marsaglia polar method, and hashrand to +// We use the Marsaglia polar method, and hashrand to OIIO_FORCEINLINE float hashnormal (int x, int y, int z, int c, int seed) { @@ -277,7 +277,7 @@ hashnormal (int x, int y, int z, int c, int seed) float M = sqrt(-2.0 * log(r2) / r2); return xr * M; } - + template @@ -298,7 +298,7 @@ noise_uniform_ (ImageBuf &dst, float min, float max, bool mono, // Serial case for (ImageBuf::Iterator p (dst, roi); !p.done(); ++p) { int x = p.x(), y = p.y(), z = p.z(); - float n; + float n = 0.0; for (int c = roi.chbegin; c < roi.chend; ++c) { if (c == 0 || !mono) n = lerp (min, max, hashrand (x, y, z, c, seed)); @@ -328,7 +328,7 @@ noise_gaussian_ (ImageBuf &dst, float mean, float stddev, bool mono, // Serial case for (ImageBuf::Iterator p (dst, roi); !p.done(); ++p) { int x = p.x(), y = p.y(), z = p.z(); - float n; + float n = 0.0; for (int c = roi.chbegin; c < roi.chend; ++c) { if (c == 0 || !mono) n = mean + stddev * hashnormal (x, y, z, c, seed); @@ -358,7 +358,7 @@ noise_salt_ (ImageBuf &dst, float saltval, float saltportion, bool mono, // Serial case for (ImageBuf::Iterator p (dst, roi); !p.done(); ++p) { int x = p.x(), y = p.y(), z = p.z(); - float n; + float n = 0.0; for (int c = roi.chbegin; c < roi.chend; ++c) { if (c == 0 || !mono) n = hashrand (x, y, z, c, seed); From 233a86b9232628bff5c474454a2cd1563cc9a719 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Mon, 19 May 2025 11:36:59 -0700 Subject: [PATCH 2/5] feat: Add initial support for Rust bindings with `TypeDesc` Signed-off-by: Scott Wilson --- .github/workflows/build-steps.yml | 11 + .github/workflows/ci.yml | 16 +- src/build-scripts/ci-rust-test.bash | 35 + src/rust/.gitignore | 3 + src/rust/Cargo.toml | 34 + src/rust/oiio-sys/.clang-format | 130 +++ src/rust/oiio-sys/.clang-tidy | 10 + src/rust/oiio-sys/Cargo.toml | 36 + src/rust/oiio-sys/build.rs | 48 + src/rust/oiio-sys/include/typedesc.h | 132 +++ src/rust/oiio-sys/src/lib.rs | 5 + src/rust/oiio-sys/src/typedesc.cpp | 241 +++++ src/rust/oiio-sys/src/typedesc.rs | 1036 ++++++++++++++++++++ src/rust/src/lib.rs | 8 + src/rust/src/typedesc.rs | 1315 ++++++++++++++++++++++++++ 15 files changed, 3059 insertions(+), 1 deletion(-) create mode 100755 src/build-scripts/ci-rust-test.bash create mode 100644 src/rust/.gitignore create mode 100644 src/rust/Cargo.toml create mode 100644 src/rust/oiio-sys/.clang-format create mode 100644 src/rust/oiio-sys/.clang-tidy create mode 100644 src/rust/oiio-sys/Cargo.toml create mode 100644 src/rust/oiio-sys/build.rs create mode 100644 src/rust/oiio-sys/include/typedesc.h create mode 100644 src/rust/oiio-sys/src/lib.rs create mode 100644 src/rust/oiio-sys/src/typedesc.cpp create mode 100644 src/rust/oiio-sys/src/typedesc.rs create mode 100644 src/rust/src/lib.rs create mode 100644 src/rust/src/typedesc.rs diff --git a/.github/workflows/build-steps.yml b/.github/workflows/build-steps.yml index 09e8d614f4..f6038f5be6 100644 --- a/.github/workflows/build-steps.yml +++ b/.github/workflows/build-steps.yml @@ -68,6 +68,8 @@ on: type: string nametag: type: string + rust: + type: string secrets: PASSED_GITHUB_TOKEN: required: false @@ -155,6 +157,15 @@ jobs: if: inputs.clang_format == '1' shell: bash run: src/build-scripts/run-clang-format.bash + - name: Rust Install Toolchain + if: inputs.rust == '1' + uses: dtolnay/rust-toolchain@4305c38b25d97ef35a8ad1f985ccf2d2242004f2 # stable + - name: Rust Test + if: inputs.rust == '1' + shell: bash + env: + OIIO_CXX_STANDARD: c++${{inputs.cxx_std}} + run: src/build-scripts/ci-rust-test.bash - name: Code coverage if: inputs.coverage == '1' run: src/build-scripts/ci-coverage.bash diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75e73cc183..0b77ec957b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -280,6 +280,7 @@ jobs: ctest_test_timeout: ${{ matrix.ctest_test_timeout }} coverage: ${{ matrix.coverage || 0 }} sonar: ${{ matrix.sonar || 0 }} + rust: ${{ matrix.rust || 0 }} strategy: fail-fast: false matrix: @@ -293,6 +294,7 @@ jobs: fmt_ver: 10.1.1 pybind11_ver: v2.10.0 setenvs: PUGIXML_VERSION=v1.13 + rust: 1 - desc: VFX2023 icc/C++17 py3.10 exr3.1 ocio2.1 qt5.15 nametag: linux-vfx2023.icc runner: ubuntu-latest @@ -307,6 +309,7 @@ jobs: OIIO_EXTRA_CPP_ARGS="-fp-model=precise" FREETYPE_VERSION=VER-2-13-0 DISABLE_libuhdr=1 + rust: 1 # For icc, use fp-model precise to eliminate needless LSB errors # that make test results differ from other platforms. - desc: VFX2023 icx/C++17 py3.10 exr3.1 ocio2.2 qt5.15 @@ -323,6 +326,7 @@ jobs: xOPENCOLORIO_CXX=g++ UHDR_CMAKE_C_COMPILER=gcc UHDR_CMAKE_CXX_COMPILER=g++ + rust: 1 # Building libuhdr with icx results in test failures # so we force using gcc/g++. - desc: VFX2024 gcc11/C++17 py3.11 exr3.2 ocio2.3 @@ -335,6 +339,7 @@ jobs: pybind11_ver: v2.12.0 benchmark: 1 setenvs: PUGIXML_VERSION=v1.14 + rust: 1 - desc: VFX2024 clang/C++17 py3.11 exr3.2 ocio2.3 nametag: linux-vfx2024.clang runner: ubuntu-latest @@ -347,6 +352,7 @@ jobs: pybind11_ver: v2.12.0 benchmark: 1 setenvs: PUGIXML_VERSION=v1.14 + rust: 1 - desc: VFX2025 gcc11/C++17 py3.11 exr3.3 ocio2.4 nametag: linux-vfx2025 runner: ubuntu-latest @@ -456,7 +462,7 @@ jobs: FREETYPE_VERSION=master QT_VERSION=0 INSTALL_OPENCV=0 # The installed OpenVDB has a TLS conflict with Python 3.8 - # Disabling the `apt-get update` in gh-installdeps.bash + # Disabling the `apt-get update` in gh-installdeps.bash # addresses a job killing problem in the GHA Ubuntu # 24.04 runners that cropped up circa May 29 2024. Maybe # it will be fixed and we can do the update again later? @@ -548,6 +554,7 @@ jobs: ctest_test_timeout: ${{ matrix.ctest_test_timeout || '800' }} coverage: ${{ matrix.coverage || 0 }} sonar: ${{ matrix.sonar || 0 }} + rust: ${{ matrix.rust || 0 }} strategy: fail-fast: false matrix: @@ -563,6 +570,7 @@ jobs: ctest_test_timeout: 1200 setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 benchmark: 1 + rust: 1 - desc: MacOS-14-ARM aclang15/C++20/py3.12 runner: macos-14 nametag: macos14-arm-py312 @@ -570,6 +578,7 @@ jobs: cxx_compiler: clang++ cxx_std: 20 python_ver: "3.12" + rust: 1 - desc: MacOS-15-ARM aclang16/C++20/py3.13 runner: macos-15 nametag: macos15-arm-py313 @@ -578,6 +587,7 @@ jobs: cxx_std: 20 python_ver: "3.13" benchmark: 1 + rust: 1 # @@ -614,6 +624,7 @@ jobs: ctest_test_timeout: ${{ matrix.ctest_test_timeout }} coverage: ${{ matrix.coverage || 0 }} sonar: ${{ matrix.sonar || 0 }} + rust: ${{ matrix.rust || 0 }} strategy: fail-fast: false matrix: @@ -625,6 +636,7 @@ jobs: generator: "Visual Studio 16 2019" python_ver: "3.9" setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 + rust: 1 - desc: Windows-2022 VS2022 runner: windows-2022 nametag: windows-2022 @@ -632,6 +644,7 @@ jobs: generator: "Visual Studio 17 2022" python_ver: "3.9" setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 + rust: 1 - desc: Windows-2025 VS2022 runner: windows-2025 nametag: windows-2025 @@ -640,3 +653,4 @@ jobs: python_ver: "3.9" setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 benchmark: 1 + rust: 1 diff --git a/src/build-scripts/ci-rust-test.bash b/src/build-scripts/ci-rust-test.bash new file mode 100755 index 0000000000..715dc576e9 --- /dev/null +++ b/src/build-scripts/ci-rust-test.bash @@ -0,0 +1,35 @@ +#!/usr/bin/env bash + +# Copyright Contributors to the OpenImageIO project. +# SPDX-License-Identifier: Apache-2.0 +# https://github.com/AcademySoftwareFoundation/OpenImageIO + +# Important: set -ex causes this whole script to terminate with error if +# any command in it fails. This is crucial for CI tests. +# (Though we let it run all the way through for code coverage workflows.) +if [[ "${CODECOV}" == "" ]]; then + set -ex +fi + +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$OpenImageIO_ROOT/lib +export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$OpenImageIO_ROOT/lib/pkgconfig + +# TEST +echo TEST +cat $OpenImageIO_ROOT/lib/pkgconfig/OpenImageIO.pc +echo END TEST +# END TEST + +echo "Using C++ STD ${OIIO_CXX_STANDARD}" + +echo "Running Rust oiio-sys tests" + +pushd src/rust/oiio-sys +time cargo test --all-features +popd + +echo "Running Rust oiio tests" + +pushd src/rust +time cargo test --all-features +popd diff --git a/src/rust/.gitignore b/src/rust/.gitignore new file mode 100644 index 0000000000..9182e2f494 --- /dev/null +++ b/src/rust/.gitignore @@ -0,0 +1,3 @@ +Cargo.lock +target/ +.vscode/ diff --git a/src/rust/Cargo.toml b/src/rust/Cargo.toml new file mode 100644 index 0000000000..511c6e0701 --- /dev/null +++ b/src/rust/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "oiio" +version = "0.1.0" +edition = "2021" +description = "High-level OpenImageIO bindings over oiio-sys" +documentation = "https://docs.rs/oiio" +authors = [ + "Scott Wilson ", + "David Aguilar ", +] +license = "Apache-2.0" +keywords = ["graphics", "images", "render", "texture", "vfx"] +categories = ["graphics", "multimedia::images", "rendering::data-formats"] +readme = "README.md" +homepage = "https://github.com/AcademySoftwareFoundation/OpenImageIO" +repository = "https://github.com/AcademySoftwareFoundation/OpenImageIO.git" + +[workspace] +members = ["oiio-sys"] + +[dependencies] +oiio-sys = { path = "oiio-sys", version = "0.1.0" } +thiserror = "2.0.12" + +[dev-dependencies] +proptest = "1.6.0" +proptest-derive = "0.5.1" + +# The docs are the same across all platforms so we only need to build once. +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] +rustc-args = ["--cfg", "docsrs"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/rust/oiio-sys/.clang-format b/src/rust/oiio-sys/.clang-format new file mode 100644 index 0000000000..cd9d35c439 --- /dev/null +++ b/src/rust/oiio-sys/.clang-format @@ -0,0 +1,130 @@ +Language: Cpp +BasedOnStyle: WebKit + +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: true +#AlignConsecutiveAssignments: Consecutive +#AlignConsecutiveBitFields: Consecutive +AlignConsecutiveDeclarations: false +#AlignConsecutiveDeclarations: None +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: true +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: All +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: TopLevel +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: false + SplitEmptyNamespace: false +BreakBeforeBinaryOperators: All +BreakBeforeBraces: WebKit +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: ".*" + Priority: 1 +IncludeIsMainRegex: "(Test)?$" +IndentCaseLabels: false +IndentExternBlock: NoIndent +IndentPPDirectives: AfterHash +IndentWidth: 4 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 3 +NamespaceIndentation: Inner +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 40 +PenaltyBreakBeforeFirstCallParameter: 100 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 75 +PenaltyReturnTypeOnItsOwnLine: 50 +PointerAlignment: Left +ReflowComments: false +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: c++17 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +#... diff --git a/src/rust/oiio-sys/.clang-tidy b/src/rust/oiio-sys/.clang-tidy new file mode 100644 index 0000000000..d40f146352 --- /dev/null +++ b/src/rust/oiio-sys/.clang-tidy @@ -0,0 +1,10 @@ +Checks: "-*,clang-analyzer-security*" +WarningsAsErrors: "" +HeaderFilterRegex: '(OpenImageIO/[a-zA-Z0-9_]+\.h)|(imageio)|(oiio)|(iv/)|(_pvt\.h)' +AnalyzeTemporaryDtors: false +User: lg +CheckOptions: + - key: modernize-use-nullptr.NullMacros + value: "NULL" + - key: modernize-use-emplace.SmartPointers + value: "OIIO::intrusive_ptr" diff --git a/src/rust/oiio-sys/Cargo.toml b/src/rust/oiio-sys/Cargo.toml new file mode 100644 index 0000000000..1e055add53 --- /dev/null +++ b/src/rust/oiio-sys/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "oiio-sys" +version = "0.1.0" +edition = "2021" +description = "Rust sys bindings for OpenImageIO https://github.com/AcademySoftwareFoundation/OpenImageIO" +documentation = "https://docs.rs/oiio-sys" +authors = [ + "Scott Wilson ", + "David Aguilar ", +] +license = "Apache-2.0" +keywords = ["graphics", "images", "render", "texture", "vfx"] +categories = ["graphics", "multimedia::images", "rendering::data-formats"] +readme = "README.md" +homepage = "https://github.com/AcademySoftwareFoundation/OpenImageIO" +repository = "https://github.com/AcademySoftwareFoundation/OpenImageIO.git" +links = "OpenImageIO" + +[dependencies] +cxx = { version = "1.0.158", features = ["c++17"] } + +[build-dependencies] +anyhow = "1.0.98" +cxx-build = "1.0.158" +pkg-config = "0.3.32" + +[dev-dependencies] +proptest = "1.6.0" +proptest-derive = "0.5.1" + +# The docs are the same across all platforms so we only need to build once. +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] +rustc-args = ["--cfg", "docsrs"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/src/rust/oiio-sys/build.rs b/src/rust/oiio-sys/build.rs new file mode 100644 index 0000000000..853fc3afca --- /dev/null +++ b/src/rust/oiio-sys/build.rs @@ -0,0 +1,48 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + +use anyhow::Result; + +const NAMES: &[&str] = &["typedesc"]; + +fn main() -> Result<()> { + // Skip linking on docs.rs: https://docs.rs/about/builds#detecting-docsrs + let building_docs = std::env::var("DOCS_RS").is_ok(); + if building_docs { + println!("cargo:rustc-cfg=docsrs"); + return Ok(()); + } + + let mut include_paths = Vec::new(); + + let pkgconfig = pkg_config::probe_library("OpenImageIO")?; + include_paths.extend(pkgconfig.include_paths); + + let std_version = std::env::var("OIIO_CXX_STANDARD").unwrap_or("c++17".to_string()); + + cxx_build::bridges(NAMES.iter().map(|s| format!("src/{}.rs", s))) + .files(NAMES.iter().map(|s| format!("src/{}.cpp", s))) + .std(&std_version) + .includes(&include_paths) + .compile("oiio-sys"); + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=src/lib.rs"); + + for name in NAMES { + println!("cargo:rerun-if-changed=src/{}.rs", name); + println!("cargo:rerun-if-changed=src/{}.cpp", name); + println!("cargo:rerun-if-changed=include/{}.h", name); + } + + for link_path in pkgconfig.link_paths { + println!("cargo:rustc-link-search=native={}", link_path.display()); + } + + for lib in pkgconfig.libs { + println!("cargo:rustc-link-lib={}", lib); + } + + Ok(()) +} diff --git a/src/rust/oiio-sys/include/typedesc.h b/src/rust/oiio-sys/include/typedesc.h new file mode 100644 index 0000000000..573f17b663 --- /dev/null +++ b/src/rust/oiio-sys/include/typedesc.h @@ -0,0 +1,132 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + +#pragma once + +#include "rust/cxx.h" +#include +#include +#include +#include + +namespace oiio_ffi { +using TypeDesc = OIIO::TypeDesc; +using BaseType = OIIO::TypeDesc::BASETYPE; +using Aggregate = OIIO::TypeDesc::AGGREGATE; +using VecSemantics = OIIO::TypeDesc::VECSEMANTICS; + +TypeDesc +typedesc_new(BaseType btype, Aggregate agg, VecSemantics semantics, + int arraylen) noexcept; + +TypeDesc +typedesc_from_basetype_arraylen(BaseType btype, int arraylen) noexcept; + +TypeDesc +typedesc_from_basetype_aggregate_arraylen(BaseType btype, Aggregate agg, + int arraylen) noexcept; + +TypeDesc +typedesc_from_string(rust::Str typestring); + +TypeDesc +typedesc_clone(const TypeDesc& t) noexcept; + +rust::Str +typedesc_as_str(const TypeDesc& typedesc); + +size_t +typedesc_numelements(const TypeDesc& typedesc) noexcept; + +size_t +typedesc_basevalues(const TypeDesc& typedesc) noexcept; + +bool +typedesc_is_array(const TypeDesc& typedesc) noexcept; + +bool +typedesc_is_unsized_array(const TypeDesc& typedesc) noexcept; + +bool +typedesc_is_sized_array(const TypeDesc& typedesc) noexcept; + +size_t +typedesc_size(const TypeDesc& typedesc) noexcept; + +TypeDesc +typedesc_elementtype(const TypeDesc& typedesc) noexcept; + +size_t +typedesc_elementsize(const TypeDesc& typedesc) noexcept; + +TypeDesc +typedesc_scalartype(const TypeDesc& typedesc); + +size_t +typedesc_basesize(const TypeDesc& typedesc) noexcept; + +bool +typedesc_is_floating_point(const TypeDesc& typedesc) noexcept; + +bool +typedesc_is_signed(const TypeDesc& typedesc) noexcept; + +bool +typedesc_is_unknown(const TypeDesc& typedesc) noexcept; + +size_t +typedesc_fromstring(TypeDesc& typedesc, rust::Str typestring); + +bool +typedesc_eq(const TypeDesc& typedesc, const TypeDesc& t) noexcept; + +bool +typedesc_ne(const TypeDesc& typedesc, const TypeDesc& t) noexcept; + +bool +typedesc_eq_basetype(const TypeDesc& t, BaseType b) noexcept; + +bool +basetype_eq_typedesc(BaseType b, const TypeDesc& t) noexcept; + +bool +typedesc_ne_basetype(const TypeDesc& t, BaseType b) noexcept; + +bool +basetype_ne_typedesc(BaseType b, const TypeDesc& t) noexcept; + +bool +typedesc_equivalent(const TypeDesc& typedesc, const TypeDesc& b) noexcept; + +bool +typedesc_is_vec2(const TypeDesc& typedesc, BaseType b) noexcept; + +bool +typedesc_is_vec3(const TypeDesc& typedesc, BaseType b) noexcept; + +bool +typedesc_is_vec4(const TypeDesc& typedesc, BaseType b) noexcept; + +bool +typedesc_is_box2(const TypeDesc& typedesc, BaseType b) noexcept; + +bool +typedesc_is_box3(const TypeDesc& typedesc, BaseType b) noexcept; + +void +typedesc_unarray(TypeDesc& typedesc) noexcept; + +bool +typedesc_lt(const TypeDesc& typedesc, const TypeDesc& x) noexcept; + +BaseType +typedesc_basetype_merge(TypeDesc a, TypeDesc b); + +BaseType +typedesc_basetype_merge_3(TypeDesc a, TypeDesc b, TypeDesc c); + +bool +typedesc_convert_type(TypeDesc srctype, rust::Slice src, + TypeDesc dsttype, rust::Slice dst, int n); +} // namespace oiio_ffi diff --git a/src/rust/oiio-sys/src/lib.rs b/src/rust/oiio-sys/src/lib.rs new file mode 100644 index 0000000000..08fe0c34e7 --- /dev/null +++ b/src/rust/oiio-sys/src/lib.rs @@ -0,0 +1,5 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + +pub mod typedesc; diff --git a/src/rust/oiio-sys/src/typedesc.cpp b/src/rust/oiio-sys/src/typedesc.cpp new file mode 100644 index 0000000000..fc9e60ff7a --- /dev/null +++ b/src/rust/oiio-sys/src/typedesc.cpp @@ -0,0 +1,241 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + +#include "oiio-sys/include/typedesc.h" +#include "rust/cxx.h" +#include +#include +#include +#include +#include + +namespace oiio_ffi { +TypeDesc +typedesc_new(BaseType btype, Aggregate agg, VecSemantics semantics, + int arraylen) noexcept +{ + return TypeDesc(btype, agg, semantics, arraylen); +} + +TypeDesc +typedesc_from_basetype_arraylen(BaseType btype, int arraylen) noexcept +{ + return TypeDesc(btype, arraylen); +} + +TypeDesc +typedesc_from_basetype_aggregate_arraylen(BaseType btype, Aggregate agg, + int arraylen) noexcept +{ + return TypeDesc(btype, agg, arraylen); +} + +TypeDesc +typedesc_from_string(rust::Str typestring) +{ + OIIO::string_view typestring_view(typestring.data(), typestring.size()); + return TypeDesc(typestring_view); +} + +TypeDesc +typedesc_clone(const TypeDesc& t) noexcept +{ + return TypeDesc(t); +} + + +rust::Str +typedesc_as_str(const TypeDesc& typedesc) +{ + return rust::Str(typedesc.c_str()); +} + +size_t +typedesc_numelements(const TypeDesc& typedesc) noexcept +{ + return typedesc.numelements(); +} + +size_t +typedesc_basevalues(const TypeDesc& typedesc) noexcept +{ + return typedesc.basevalues(); +} + +bool +typedesc_is_array(const TypeDesc& typedesc) noexcept +{ + return typedesc.is_array(); +} + +bool +typedesc_is_unsized_array(const TypeDesc& typedesc) noexcept +{ + return typedesc.is_unsized_array(); +} + +bool +typedesc_is_sized_array(const TypeDesc& typedesc) noexcept +{ + return typedesc.is_sized_array(); +} + +size_t +typedesc_size(const TypeDesc& typedesc) noexcept +{ + return typedesc.size(); +} + +TypeDesc +typedesc_elementtype(const TypeDesc& typedesc) noexcept +{ + return typedesc.elementtype(); +} + +size_t +typedesc_elementsize(const TypeDesc& typedesc) noexcept +{ + return typedesc.elementsize(); +} + +TypeDesc +typedesc_scalartype(const TypeDesc& typedesc) +{ + return typedesc.scalartype(); +} + +size_t +typedesc_basesize(const TypeDesc& typedesc) noexcept +{ + return typedesc.basesize(); +} + +bool +typedesc_is_floating_point(const TypeDesc& typedesc) noexcept +{ + return typedesc.is_floating_point(); +} + +bool +typedesc_is_signed(const TypeDesc& typedesc) noexcept +{ + return typedesc.is_signed(); +} + +bool +typedesc_is_unknown(const TypeDesc& typedesc) noexcept +{ + return typedesc.is_unknown(); +} + +size_t +typedesc_fromstring(TypeDesc& typedesc, rust::Str typestring) +{ + return typedesc.fromstring( + std::string_view(typestring.data(), typestring.size())); +} + +bool +typedesc_eq(const TypeDesc& typedesc, const TypeDesc& t) noexcept +{ + return typedesc == t; +} + +bool +typedesc_ne(const TypeDesc& typedesc, const TypeDesc& t) noexcept +{ + return typedesc != t; +} + +bool +typedesc_eq_basetype(const TypeDesc& t, BaseType b) noexcept +{ + return t == b; +} + +bool +basetype_eq_typedesc(BaseType b, const TypeDesc& t) noexcept +{ + return b == t; +} + +bool +typedesc_ne_basetype(const TypeDesc& t, BaseType b) noexcept +{ + return t != b; +} + +bool +basetype_ne_typedesc(BaseType b, const TypeDesc& t) noexcept +{ + return b != t; +} + +bool +typedesc_equivalent(const TypeDesc& typedesc, const TypeDesc& b) noexcept +{ + return typedesc.equivalent(b); +} + +bool +typedesc_is_vec2(const TypeDesc& typedesc, BaseType b) noexcept +{ + return typedesc.is_vec2(b); +} + +bool +typedesc_is_vec3(const TypeDesc& typedesc, BaseType b) noexcept +{ + return typedesc.is_vec3(b); +} + +bool +typedesc_is_vec4(const TypeDesc& typedesc, BaseType b) noexcept +{ + return typedesc.is_vec4(b); +} + +bool +typedesc_is_box2(const TypeDesc& typedesc, BaseType b) noexcept +{ + return typedesc.is_box2(b); +} + +bool +typedesc_is_box3(const TypeDesc& typedesc, BaseType b) noexcept +{ + return typedesc.is_box3(b); +} + +void +typedesc_unarray(TypeDesc& typedesc) noexcept +{ + typedesc.unarray(); +} + +bool +typedesc_lt(const TypeDesc& typedesc, const TypeDesc& x) noexcept +{ + return typedesc < x; +} + +BaseType +typedesc_basetype_merge(TypeDesc a, TypeDesc b) +{ + return TypeDesc::basetype_merge(a, b); +} + +BaseType +typedesc_basetype_merge_3(TypeDesc a, TypeDesc b, TypeDesc c) +{ + return TypeDesc::basetype_merge(a, b, c); +} + +bool +typedesc_convert_type(TypeDesc srctype, rust::Slice src, + TypeDesc dsttype, rust::Slice dst, int n) +{ + return convert_type(srctype, src.data(), dsttype, dst.data(), n); +} +} // namespace oiio_ffi diff --git a/src/rust/oiio-sys/src/typedesc.rs b/src/rust/oiio-sys/src/typedesc.rs new file mode 100644 index 0000000000..7d06049d9b --- /dev/null +++ b/src/rust/oiio-sys/src/typedesc.rs @@ -0,0 +1,1036 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + +pub use ffi::*; + +/// A TypeDesc describes simple data types. +/// +/// It frequently comes up (in my experience, with renderers and image +/// handling programs) that you want a way to describe data that is passed +/// through APIs through blind pointers. These are some simple classes +/// that provide a simple type descriptor system. This is not meant to +/// be comprehensive -- for example, there is no provision for structs, +/// unions, typed pointers, const, or 'nested' type definitions. Just simple +/// integer and floating point, *common* aggregates such as 3-points, and +/// reasonably-lengthed arrays thereof. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct TypeDesc { + /// C data type at the heart of our type + pub basetype: u8, + /// What kind of AGGREGATE is it? + pub aggregate: u8, + /// Hint: What does the aggregate represent? + pub vecsemantics: u8, + /// Reserved for future expansion + _reserved: u8, + /// Array length, 0 = not array, -1 = unsized + pub arraylen: i32, +} + +unsafe impl cxx::ExternType for TypeDesc { + type Id = cxx::type_id!("oiio_ffi::TypeDesc"); + type Kind = cxx::kind::Trivial; +} + +#[cxx::bridge(namespace = oiio_ffi)] +mod ffi { + /// BaseType is a simple enum describing the base data types that + /// correspond (mostly) to the C/C++ built-in types. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + #[repr(u32)] + pub enum BaseType { + /// unknown type + UNKNOWN = 0, + /// void/no type + NONE = 1, + /// 8-bit unsigned int values ranging from 0..255, + /// (C/C++ `unsigned char`). + UINT8 = 2, + /// 8-bit int values ranging from -128..127, + /// (C/C++ `char`). + INT8 = 3, + /// 16-bit int values ranging from 0..65535, + /// (C/C++ `unsigned short`). + UINT16 = 4, + /// 16-bit int values ranging from -32768..32767, + /// (C/C++ `short`). + INT16 = 5, + /// 32-bit unsigned int values (C/C++ `unsigned int`). + UINT32 = 6, + /// signed 32-bit int values (C/C++ `int`). + INT32 = 7, + /// 64-bit unsigned int values (C/C++ + /// `unsigned long long` on most architectures). + UINT64 = 8, + /// signed 64-bit int values (C/C++ `long long` + /// on most architectures). + INT64 = 9, + /// 16-bit IEEE floating point values (OpenEXR `half`). + HALF = 10, + /// 32-bit IEEE floating point values, (C/C++ `float`). + FLOAT = 11, + /// 64-bit IEEE floating point values, (C/C++ `double`). + DOUBLE = 12, + /// Character string. + STRING = 13, + /// A pointer value. + PTR = 14, + /// A uint64 that is the hash of a ustring. + USTRINGHASH = 15, + LASTBASE = 16, + } + + /// Aggregate describes whether our TypeDesc is a simple scalar of one + /// of the BaseType's, or one of several simple aggregates. + /// + /// Note that aggregates and arrays are different. A `TypeDesc(FLOAT,3)` + /// is an array of three floats, a `TypeDesc(FLOAT,VEC3)` is a single + /// 3-component vector comprised of floats, and `TypeDesc(FLOAT,3,VEC3)` + /// is an array of 3 vectors, each of which is comprised of 3 floats. + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + #[repr(u32)] + pub enum Aggregate { + /// A single scalar value (such as a raw `int` or + /// `float` in C). This is the default. + SCALAR = 1, + /// 2 values representing a 2D vector. + VEC2 = 2, + /// 3 values representing a 3D vector. + VEC3 = 3, + /// 4 values representing a 4D vector. + VEC4 = 4, + /// 9 values representing a 3x3 matrix. + MATRIX33 = 9, + /// 16 values representing a 4x4 matrix. + MATRIX44 = 16, + } + + /// VecSemantics gives hints about what the data represent (for example, + /// if a spatial vector quantity should transform as a point, direction + /// vector, or surface normal). + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] + #[repr(u32)] + pub enum VecSemantics { + /// No semantic hints. + NOSEMANTICS = 0, + /// Color + COLOR = 1, + /// Point: a spatial location + POINT = 2, + /// Vector: a spatial direction + VECTOR = 3, + /// Normal: a surface normal + NORMAL = 4, + /// indicates an `int[2]` representing the standard + /// 4-byte encoding of an SMPTE timecode. + TIMECODE = 5, + /// indicates an `int[7]` representing the standard + /// 28-byte encoding of an SMPTE keycode. + KEYCODE = 6, + /// A `VEC2` representing a rational number `val[0] / val[1]` + RATIONAL = 7, + /// A `VEC2[2]` or `VEC3[2]` that represents a 2D or 3D bounds (min/max) + BOX = 8, + } + + unsafe extern "C++" { + include!("oiio-sys/include/typedesc.h"); + + // TypeDesc + type BaseType; + type Aggregate; + type VecSemantics; + type TypeDesc = crate::typedesc::TypeDesc; + + /// Construct from a BaseType and optional aggregateness, semantics, + /// and arrayness. + fn typedesc_new( + btype: BaseType, + agg: Aggregate, + semantics: VecSemantics, + arraylen: i32, + ) -> TypeDesc; + + /// Construct an array of a non-aggregate BaseType. + fn typedesc_from_basetype_arraylen(btype: BaseType, arraylen: i32) -> TypeDesc; + + /// Construct an array from BaseType, Aggregate, and array length, + /// with unspecified (or moot) semantic hints. + fn typedesc_from_basetype_aggregate_arraylen( + btype: BaseType, + agg: Aggregate, + arraylen: i32, + ) -> TypeDesc; + + /// Construct from a string (e.g., `float[3]`). If no valid + /// type could be assembled, set base to `UNKNOWN`. + /// + /// Examples: + /// + /// ``` + /// # use oiio_sys::typedesc::*; + /// assert!(typedesc_eq(&typedesc_from_string("int"), &typedesc_new(BaseType::INT32, Aggregate::SCALAR, VecSemantics::NOSEMANTICS, 0))); + /// assert!(typedesc_eq(&typedesc_from_string("float"), &typedesc_new(BaseType::FLOAT, Aggregate::SCALAR, VecSemantics::NOSEMANTICS, 0))); + /// assert!(typedesc_eq(&typedesc_from_string("uint16"), &typedesc_new(BaseType::UINT16, Aggregate::SCALAR, VecSemantics::NOSEMANTICS, 0))); + /// assert!(typedesc_eq(&typedesc_from_string("point"), &typedesc_new(BaseType::FLOAT, Aggregate::VEC3, VecSemantics::POINT, 0))); + /// ``` + /// + fn typedesc_from_string(typestring: &str) -> TypeDesc; + + /// Clone constructor. + fn typedesc_clone(t: &TypeDesc) -> TypeDesc; + + /// Display the name, for printing and whatnot. For example, + /// `float`, `int[5]`, `normal` + fn typedesc_as_str(typedesc: &TypeDesc) -> &str; + + /// Return the number of elements: 1 if not an array, or the array + /// length. Invalid to call this for arrays of undetermined size. + fn typedesc_numelements(typedesc: &TypeDesc) -> usize; + + /// Return the number of basetype values: the aggregate count multiplied + /// by the array length (or 1 if not an array). Invalid to call this + /// for arrays of undetermined size. + fn typedesc_basevalues(typedesc: &TypeDesc) -> usize; + + /// Does this TypeDesc describe an array? + fn typedesc_is_array(typedesc: &TypeDesc) -> bool; + + /// Does this TypeDesc describe an array, but whose length is not + /// specified? + fn typedesc_is_unsized_array(typedesc: &TypeDesc) -> bool; + + /// Does this TypeDesc describe an array, whose length is specified? + fn typedesc_is_sized_array(typedesc: &TypeDesc) -> bool; + + /// Return the size, in bytes, of this type. + fn typedesc_size(typedesc: &TypeDesc) -> usize; + + /// Return the type of one element, i.e., strip out the array-ness. + fn typedesc_elementtype(typedesc: &TypeDesc) -> TypeDesc; + + /// Return the size, in bytes, of one element of this type (that is, + /// ignoring whether it's an array). + fn typedesc_elementsize(typedesc: &TypeDesc) -> usize; + + /// Return just the underlying C scalar type, i.e., strip out the + /// array-ness and the aggregateness. + fn typedesc_scalartype(typedesc: &TypeDesc) -> TypeDesc; + + /// Return the base type size, i.e., stripped of both array-ness + /// and aggregateness. + fn typedesc_basesize(typedesc: &TypeDesc) -> usize; + + /// True if it's a floating-point type (versus a fundamentally + /// integral type or something else like a string). + fn typedesc_is_floating_point(typedesc: &TypeDesc) -> bool; + + /// True if it's a signed type that allows for negative values. + fn typedesc_is_signed(typedesc: &TypeDesc) -> bool; + + /// Shortcut: is it UNKNOWN? + fn typedesc_is_unknown(typedesc: &TypeDesc) -> bool; + + /// Set `typedesc` to the type described in the string. Return the + /// length of the part of the string that describes the type. If + /// no valid type could be assembled, return 0 and do not modify + /// `typedesc`. + fn typedesc_fromstring(typedesc: &mut TypeDesc, typestring: &str) -> usize; + + /// Compare two TypeDesc values for equality. + fn typedesc_eq(typedesc: &TypeDesc, t: &TypeDesc) -> bool; + + /// Compare two TypeDesc values for inequality. + fn typedesc_ne(typedesc: &TypeDesc, t: &TypeDesc) -> bool; + + /// Compare a TypeDesc to a basetype (it's the same if it has the + /// same base type and is not an aggregate or an array). + fn typedesc_eq_basetype(t: &TypeDesc, b: BaseType) -> bool; + + /// Compare a TypeDesc to a basetype (it's the same if it has the + /// same base type and is not an aggregate or an array). + fn basetype_eq_typedesc(b: BaseType, t: &TypeDesc) -> bool; + + /// Compare a TypeDesc to a basetype (it's the same if it has the + /// same base type and is not an aggregate or an array). + fn typedesc_ne_basetype(t: &TypeDesc, b: BaseType) -> bool; + + /// Compare a TypeDesc to a basetype (it's the same if it has the + /// same base type and is not an aggregate or an array). + fn basetype_ne_typedesc(b: BaseType, t: &TypeDesc) -> bool; + + /// TypeDesc's are equivalent if they are equal, or if their only + /// inequality is differing vector semantics. + fn typedesc_equivalent(typedesc: &TypeDesc, b: &TypeDesc) -> bool; + + /// Is this a 2-vector aggregate (of the given type)? + fn typedesc_is_vec2(typedesc: &TypeDesc, b: BaseType) -> bool; + + /// Is this a 3-vector aggregate (of the given type)? + fn typedesc_is_vec3(typedesc: &TypeDesc, b: BaseType) -> bool; + + /// Is this a 4-vector aggregate (of the given type)? + fn typedesc_is_vec4(typedesc: &TypeDesc, b: BaseType) -> bool; + + /// Is this an array of aggregates that represents a 2D bounding box? + fn typedesc_is_box2(typedesc: &TypeDesc, b: BaseType) -> bool; + + /// Is this an array of aggregates that represents a 3D bounding box? + fn typedesc_is_box3(typedesc: &TypeDesc, b: BaseType) -> bool; + + /// Demote the type to a non-array + fn typedesc_unarray(typedesc: &mut TypeDesc) -> (); + + /// Test for lexicographic 'less', comes in handy for lots of STL + /// containers and algorithms. + fn typedesc_lt(typedesc: &TypeDesc, x: &TypeDesc) -> bool; + + /// Given base data types of a and b, return a basetype that is a best + /// guess for one that can handle both without any loss of range or + /// precision. + fn typedesc_basetype_merge(a: TypeDesc, b: TypeDesc) -> BaseType; + + /// Given base data types of a and b, return a basetype that is a best + /// guess for one that can handle both without any loss of range or + /// precision. + fn typedesc_basetype_merge_3(a: TypeDesc, b: TypeDesc, c: TypeDesc) -> BaseType; + + /// Given data pointed to by src and described by srctype, copy it to the + /// memory pointed to by dst and described by dsttype, and return true if a + /// conversion is possible, false if it is not. If the types are equivalent, + /// this is a straightforward memory copy. If the types differ, there are + /// several non-equivalent type conversions that will nonetheless succeed: + /// * If dsttype is a string (and therefore dst points to a ustring or a + /// `char*`): it will always succeed, producing a string akin to calling + /// `typedesc_tostring()`. + /// * If dsttype is int32 or uint32: other integer types will do their best + /// (caveat emptor if you mix signed/unsigned). Also a source string will + /// convert to int if and only if its characters form a valid integer. + /// * If dsttype is float: integers and other float types will do + /// their best conversion; strings will convert if and only if their + /// characters form a valid float number. + fn typedesc_convert_type( + srctype: TypeDesc, + src: &[u8], + dsttype: TypeDesc, + dst: &mut [u8], + n: i32, + ) -> bool; + } +} + +#[cfg(test)] +mod tests { + use proptest::prelude::*; + + use super::{Aggregate, BaseType, VecSemantics, *}; + + fn basetypes() -> impl Strategy { + prop_oneof![ + Just(BaseType::UNKNOWN), + Just(BaseType::NONE), + Just(BaseType::UINT8), + Just(BaseType::INT8), + Just(BaseType::UINT16), + Just(BaseType::INT16), + Just(BaseType::UINT32), + Just(BaseType::INT32), + Just(BaseType::UINT64), + Just(BaseType::INT64), + Just(BaseType::HALF), + Just(BaseType::FLOAT), + Just(BaseType::DOUBLE), + Just(BaseType::STRING), + Just(BaseType::PTR), + Just(BaseType::USTRINGHASH), + Just(BaseType::LASTBASE), + ] + } + + fn aggregates() -> impl Strategy { + prop_oneof![ + Just(Aggregate::SCALAR), + Just(Aggregate::VEC2), + Just(Aggregate::VEC3), + Just(Aggregate::VEC4), + Just(Aggregate::MATRIX33), + Just(Aggregate::MATRIX44), + ] + } + + fn vecsemantics() -> impl Strategy { + prop_oneof![ + Just(VecSemantics::NOSEMANTICS), + Just(VecSemantics::COLOR), + Just(VecSemantics::POINT), + Just(VecSemantics::VECTOR), + Just(VecSemantics::NORMAL), + Just(VecSemantics::TIMECODE), + Just(VecSemantics::KEYCODE), + Just(VecSemantics::RATIONAL), + Just(VecSemantics::BOX), + ] + } + + prop_compose! { + fn typedescs()( + basetype in basetypes(), + aggregate in aggregates(), + vecsemantics in vecsemantics(), + arraylen in i32::MIN..i32::MAX + ) -> TypeDesc { + TypeDesc { + basetype: basetype.repr as u8, + aggregate: aggregate.repr as u8, + vecsemantics: vecsemantics.repr as u8, + _reserved: 0, + arraylen, + } + } + } + + fn from_string_strategy() -> impl Strategy { + basetypes().prop_filter_map("BaseType::LASTBASE is unsupported", |base_type| { + let base_type_str = match base_type { + BaseType::UNKNOWN => "unknown", + BaseType::NONE => "void", + BaseType::UINT8 => "uint8", + BaseType::INT8 => "int8", + BaseType::UINT16 => "uint16", + BaseType::INT16 => "int16", + BaseType::UINT32 => "uint", + BaseType::INT32 => "int", + BaseType::UINT64 => "uint64", + BaseType::INT64 => "int64", + BaseType::HALF => "half", + BaseType::FLOAT => "float", + BaseType::DOUBLE => "double", + BaseType::STRING => "string", + BaseType::PTR => "pointer", + BaseType::USTRINGHASH => "ustringhash", + BaseType::LASTBASE => return None, + _ => unreachable!(), + }; + + Some((typedesc_from_basetype_arraylen(base_type, 0), base_type_str)) + }) + } + + proptest! { + #[test] + fn test_typedesc_new_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in i32::MIN..i32::MAX + ) { + let result = typedesc_new(btype, agg, semantics, arraylen); + + prop_assert_eq!(result.basetype, btype.repr as u8); + prop_assert_eq!(result.aggregate, agg.repr as u8); + prop_assert_eq!(result.vecsemantics, semantics.repr as u8); + prop_assert_eq!(result.arraylen, arraylen); + } + + #[test] + fn test_typedesc_from_basetype_arraylen_success( + btype in basetypes(), + arraylen in i32::MIN..i32::MAX + ) { + let result = typedesc_from_basetype_arraylen(btype, arraylen); + + prop_assert_eq!(result.basetype, btype.repr as u8); + prop_assert_eq!(result.aggregate, Aggregate::SCALAR.repr as u8); + prop_assert_eq!(result.vecsemantics, VecSemantics::NOSEMANTICS.repr as u8); + prop_assert_eq!(result.arraylen, arraylen); + } + + #[test] + fn test_typedesc_from_basetype_aggregate_arraylen_success( + btype in basetypes(), + agg in aggregates(), + arraylen in any::() + ) { + let result = typedesc_from_basetype_aggregate_arraylen(btype, agg, arraylen); + + prop_assert_eq!(result.basetype, btype.repr as u8); + prop_assert_eq!(result.aggregate, agg.repr as u8); + prop_assert_eq!(result.vecsemantics, VecSemantics::NOSEMANTICS.repr as u8); + prop_assert_eq!(result.arraylen, arraylen); + } + + #[test] + fn test_typedesc_from_string_success( + (expected_type_desc, input_str) in from_string_strategy() + ) { + let result = typedesc_from_string(&input_str); + + prop_assert_eq!(result.basetype, expected_type_desc.basetype); + prop_assert_eq!(result.aggregate, expected_type_desc.aggregate); + prop_assert_eq!(result.vecsemantics, expected_type_desc.vecsemantics); + prop_assert_eq!(result.arraylen, expected_type_desc.arraylen); + } + + #[test] + fn test_typedesc_clone_success( + type_desc in typedescs(), + ) { + let result = typedesc_clone(&type_desc); + + prop_assert_eq!(result.basetype, type_desc.basetype); + prop_assert_eq!(result.aggregate, type_desc.aggregate); + prop_assert_eq!(result.vecsemantics, type_desc.vecsemantics); + prop_assert_eq!(result.arraylen, type_desc.arraylen); + } + + #[test] + fn test_typedesc_as_str_success( + (input_type_desc, expected_str) in from_string_strategy() + ) { + let result = typedesc_as_str(&input_type_desc); + + prop_assert_eq!(result, expected_str); + } + + #[test] + fn test_typedesc_numelements_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in 1..i32::MAX + ) { + let typedesc = typedesc_new(btype, agg, semantics, arraylen); + let result = typedesc_numelements(&typedesc); + + prop_assert_eq!(result, arraylen as usize); + } + + #[test] + fn test_typedesc_basevalues_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in 1..i32::MAX + ) { + let typedesc = typedesc_new(btype, agg, semantics, arraylen); + let result = typedesc_basevalues(&typedesc); + + prop_assert_eq!(result, arraylen as usize * agg.repr as usize); + } + + #[test] + fn test_typedesc_is_array_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in -2..2 + ) { + let typedesc = typedesc_new(btype, agg, semantics, arraylen); + let result = typedesc_is_array(&typedesc); + + prop_assert_eq!(result, arraylen != 0); + } + + #[test] + fn test_typedesc_is_unsized_array_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in -2..2 + ) { + let typedesc = typedesc_new(btype, agg, semantics, arraylen); + let result = typedesc_is_unsized_array(&typedesc); + + prop_assert_eq!(result, arraylen < 0); + } + + #[test] + fn test_typedesc_is_sized_array_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in -2..2 + ) { + let typedesc = typedesc_new(btype, agg, semantics, arraylen); + let result = typedesc_is_sized_array(&typedesc); + + prop_assert_eq!(result, arraylen > 0); + } + + #[test] + fn test_typedesc_size_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in 1..2 + ) { + let typedesc = typedesc_new(btype, agg, semantics, arraylen); + let result = typedesc_size(&typedesc); + + prop_assert_eq!(result, arraylen as usize * typedesc_elementsize(&typedesc)); + } + + #[test] + fn test_typedesc_elementtype_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in i32::MIN..i32::MAX + ) { + let typedesc = typedesc_new(btype, agg, semantics, arraylen); + let result = typedesc_elementtype(&typedesc); + + prop_assert_eq!(result.basetype, btype.repr as u8); + prop_assert_eq!(result.aggregate, agg.repr as u8); + prop_assert_eq!(result.vecsemantics, semantics.repr as u8); + prop_assert_eq!(result.arraylen, 0); + } + + #[test] + fn test_typedesc_elementsize_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in i32::MIN..i32::MAX + ) { + let typedesc = typedesc_new(btype, agg, semantics, arraylen); + let result = typedesc_elementsize(&typedesc); + + prop_assert_eq!(result, agg.repr as usize * typedesc_basesize(&typedesc)); + } + + #[test] + fn test_typedesc_scalartype_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in i32::MIN..i32::MAX + ) { + let typedesc = typedesc_new(btype, agg, semantics, arraylen); + let result = typedesc_scalartype(&typedesc); + + prop_assert_eq!(result.basetype, btype.repr as u8); + prop_assert_eq!(result.aggregate, Aggregate::SCALAR.repr as u8); + prop_assert_eq!(result.vecsemantics, VecSemantics::NOSEMANTICS.repr as u8); + prop_assert_eq!(result.arraylen, 0); + } + + #[test] + fn test_typedesc_basesize_success( + btype in basetypes().prop_filter("BaseType::LASTBASE not supported", |btype| !matches!(*btype, crate::typedesc::BaseType::LASTBASE)), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in i32::MIN..i32::MAX + ) { + let typedesc = typedesc_new(btype, agg, semantics, arraylen); + let result = typedesc_basesize(&typedesc); + + let expected = match btype { + BaseType::UNKNOWN => 0, + BaseType::NONE => 0, + BaseType::UINT8 => size_of::(), + BaseType::INT8 => size_of::(), + BaseType::UINT16 => size_of::(), + BaseType::INT16 => size_of::(), + BaseType::UINT32 => size_of::(), + BaseType::INT32 => size_of::(), + BaseType::UINT64 => size_of::(), + BaseType::INT64 => size_of::(), + BaseType::HALF => size_of::() / 2, + BaseType::FLOAT => size_of::(), + BaseType::DOUBLE => size_of::(), + BaseType::STRING => size_of::<*const std::ffi::c_char>(), + BaseType::PTR => size_of::<*const std::ffi::c_char>(), + BaseType::USTRINGHASH => 8, // TODO: Get the size of the UStringHash type. + BaseType::LASTBASE => unreachable!(), + _ => unreachable!(), + }; + + prop_assert_eq!(result, expected); + } + + #[test] + fn test_typedesc_is_floating_point_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in i32::MIN..i32::MAX + ) { + let typedesc = typedesc_new(btype, agg, semantics, arraylen); + let result = typedesc_is_floating_point(&typedesc); + + prop_assert_eq!(result, matches!(btype, BaseType::HALF | BaseType::FLOAT | BaseType::DOUBLE)); + } + + #[test] + fn test_typedesc_is_unknown_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in i32::MIN..i32::MAX + ) { + let typedesc = typedesc_new(btype, agg, semantics, arraylen); + let result = typedesc_is_unknown(&typedesc); + + prop_assert_eq!(result, matches!(btype, BaseType::UNKNOWN)); + } + + #[test] + fn test_typedesc_fromstring_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in i32::MIN..i32::MAX, + (expected_type_desc, input_str) in from_string_strategy() + ) { + let mut typedesc = typedesc_new(btype, agg, semantics, arraylen); + typedesc_fromstring(&mut typedesc, &input_str); + + prop_assert_eq!(typedesc.basetype, expected_type_desc.basetype); + prop_assert_eq!(typedesc.aggregate, expected_type_desc.aggregate); + prop_assert_eq!(typedesc.vecsemantics, expected_type_desc.vecsemantics); + prop_assert_eq!(typedesc.arraylen, expected_type_desc.arraylen); + } + + #[test] + fn test_typedesc_eq_success( + btype_a in basetypes(), + agg_a in aggregates(), + semantics_a in vecsemantics(), + arraylen_a in i32::MIN..i32::MAX, + btype_b in basetypes(), + agg_b in aggregates(), + semantics_b in vecsemantics(), + arraylen_b in i32::MIN..i32::MAX, + ) { + let typedesc_a = typedesc_new(btype_a, agg_a, semantics_a, arraylen_a); + let typedesc_b = typedesc_new(btype_b, agg_b, semantics_b, arraylen_b); + + if btype_a == btype_b && agg_a == agg_b && semantics_a == semantics_b && arraylen_a == arraylen_b { + prop_assert!(typedesc_eq(&typedesc_a, &typedesc_b)); + } else { + prop_assert!(!typedesc_eq(&typedesc_a, &typedesc_b)); + } + } + + #[test] + fn test_typedesc_ne_success( + btype_a in basetypes(), + agg_a in aggregates(), + semantics_a in vecsemantics(), + arraylen_a in i32::MIN..i32::MAX, + btype_b in basetypes(), + agg_b in aggregates(), + semantics_b in vecsemantics(), + arraylen_b in i32::MIN..i32::MAX, + ) { + let typedesc_a = typedesc_new(btype_a, agg_a, semantics_a, arraylen_a); + let typedesc_b = typedesc_new(btype_b, agg_b, semantics_b, arraylen_b); + + if btype_a == btype_b && agg_a == agg_b && semantics_a == semantics_b && arraylen_a == arraylen_b { + prop_assert!(!typedesc_ne(&typedesc_a, &typedesc_b)); + } else { + prop_assert!(typedesc_ne(&typedesc_a, &typedesc_b)); + } + } + + #[test] + fn test_typedesc_eq_basetype_success( + btype_a in basetypes(), + agg_a in aggregates(), + semantics_a in vecsemantics(), + arraylen_a in -2..2, + btype_b in basetypes(), + ) { + let typedesc_a = typedesc_new(btype_a, agg_a, semantics_a, arraylen_a); + + if btype_a == btype_b && agg_a == Aggregate::SCALAR && !typedesc_is_array(&typedesc_a) { + prop_assert!(typedesc_eq_basetype(&typedesc_a, btype_b)); + prop_assert!(basetype_eq_typedesc(btype_b, &typedesc_a)); + } else { + prop_assert!(!typedesc_eq_basetype(&typedesc_a, btype_b)); + prop_assert!(!basetype_eq_typedesc(btype_b, &typedesc_a)); + } + } + + #[test] + fn test_typedesc_ne_basetype_success( + btype_a in basetypes(), + agg_a in aggregates(), + semantics_a in vecsemantics(), + arraylen_a in -2..2, + btype_b in basetypes(), + ) { + let typedesc_a = typedesc_new(btype_a, agg_a, semantics_a, arraylen_a); + + if btype_a == btype_b && agg_a == Aggregate::SCALAR && !typedesc_is_array(&typedesc_a) { + prop_assert!(!typedesc_ne_basetype(&typedesc_a, btype_b)); + prop_assert!(!basetype_ne_typedesc(btype_b, &typedesc_a)); + } else { + prop_assert!(typedesc_ne_basetype(&typedesc_a, btype_b)); + prop_assert!(basetype_ne_typedesc(btype_b, &typedesc_a)); + } + } + + #[test] + fn test_typedesc_equivalent_success( + btype_a in basetypes(), + agg_a in aggregates(), + semantics_a in vecsemantics(), + btype_b in basetypes(), + agg_b in aggregates(), + semantics_b in vecsemantics(), + ) { + let typedesc_a = typedesc_new(btype_a, agg_a, semantics_a, 0); + let typedesc_b = typedesc_new(btype_b, agg_b, semantics_b, 0); + + if btype_a == btype_b && agg_a == agg_b { + prop_assert!(typedesc_equivalent(&typedesc_a, &typedesc_b)); + } else { + prop_assert!(!typedesc_equivalent(&typedesc_a, &typedesc_b)); + } + } + + #[test] + fn test_typedesc_is_vec2_success( + btype in basetypes(), + btype_b in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in -2..2 + ) { + let result = typedesc_new(btype, agg, semantics, arraylen); + + if result.aggregate == Aggregate::VEC2.repr as u8 && result.basetype == btype_b.repr as u8 && !typedesc_is_array(&result) { + prop_assert!(typedesc_is_vec2(&result, btype_b)); + } else { + prop_assert!(!typedesc_is_vec2(&result, btype_b)); + + } + } + + #[test] + fn test_typedesc_is_vec3_success( + btype in basetypes(), + btype_b in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in -2..2 + ) { + let result = typedesc_new(btype, agg, semantics, arraylen); + + if result.aggregate == Aggregate::VEC3.repr as u8 && result.basetype == btype_b.repr as u8 && !typedesc_is_array(&result) { + prop_assert!(typedesc_is_vec3(&result, btype_b)); + } else { + prop_assert!(!typedesc_is_vec3(&result, btype_b)); + + } + } + + #[test] + fn test_typedesc_is_vec4_success( + btype in basetypes(), + btype_b in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in -2..2 + ) { + let result = typedesc_new(btype, agg, semantics, arraylen); + + if result.aggregate == Aggregate::VEC4.repr as u8 && result.basetype == btype_b.repr as u8 && !typedesc_is_array(&result) { + prop_assert!(typedesc_is_vec4(&result, btype_b)); + } else { + prop_assert!(!typedesc_is_vec4(&result, btype_b)); + + } + } + + #[test] + fn test_typedesc_is_box2_success( + btype in basetypes(), + btype_b in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in -2..4 + ) { + let result = typedesc_new(btype, agg, semantics, arraylen); + + if result.aggregate == Aggregate::VEC2.repr as u8 && result.vecsemantics == VecSemantics::BOX.repr as u8 && result.basetype == btype_b.repr as u8 && arraylen == 2 { + prop_assert!(typedesc_is_box2(&result, btype_b)); + } else { + prop_assert!(!typedesc_is_box2(&result, btype_b)); + + } + } + + #[test] + fn test_typedesc_is_box3_success( + btype in basetypes(), + btype_b in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in -2..4 + ) { + let result = typedesc_new(btype, agg, semantics, arraylen); + + if result.aggregate == Aggregate::VEC3.repr as u8 && result.vecsemantics == VecSemantics::BOX.repr as u8 && result.basetype == btype_b.repr as u8 && arraylen == 2 { + prop_assert!(typedesc_is_box3(&result, btype_b)); + } else { + prop_assert!(!typedesc_is_box3(&result, btype_b)); + + } + } + + #[test] + fn test_typedesc_unarray_success( + btype in basetypes(), + agg in aggregates(), + semantics in vecsemantics(), + arraylen in i32::MIN..i32::MAX + ) { + let mut result = typedesc_new(btype, agg, semantics, arraylen); + typedesc_unarray(&mut result); + + prop_assert_eq!(result.arraylen, 0); + } + + #[test] + fn test_typedesc_lt_success( + btype_a in basetypes(), + agg_a in aggregates(), + semantics_a in vecsemantics(), + arraylen_a in i32::MIN..i32::MAX, + btype_b in basetypes(), + agg_b in aggregates(), + semantics_b in vecsemantics(), + arraylen_b in i32::MIN..i32::MAX, + ) { + let typedesc_a = typedesc_new(btype_a, agg_a, semantics_a, arraylen_a); + let typedesc_b = typedesc_new(btype_b, agg_b, semantics_b, arraylen_b); + + if btype_a != btype_b { + prop_assert_eq!(typedesc_lt(&typedesc_a, &typedesc_b), btype_a < btype_b); + } else if agg_a != agg_b { + prop_assert_eq!(typedesc_lt(&typedesc_a, &typedesc_b), agg_a < agg_b); + } else if arraylen_a != arraylen_b { + prop_assert_eq!(typedesc_lt(&typedesc_a, &typedesc_b), arraylen_a < arraylen_b); + } else if semantics_a != semantics_b { + prop_assert_eq!(typedesc_lt(&typedesc_a, &typedesc_b), semantics_a < semantics_b); + } else { + prop_assert_eq!(typedesc_lt(&typedesc_a, &typedesc_b), false); + } + } + + #[test] + fn test_typedesc_basetype_merge_success( + mut btype_a in basetypes(), + agg_a in aggregates(), + semantics_a in vecsemantics(), + arraylen_a in i32::MIN..i32::MAX, + mut btype_b in basetypes(), + agg_b in aggregates(), + semantics_b in vecsemantics(), + arraylen_b in i32::MIN..i32::MAX, + ) { + let typedesc_a = typedesc_new(btype_a, agg_a, semantics_a, arraylen_a); + let typedesc_b = typedesc_new(btype_b, agg_b, semantics_b, arraylen_b); + let result = typedesc_basetype_merge(typedesc_a, typedesc_b); + + if btype_a == btype_b { + prop_assert_eq!(result, btype_a); + prop_assert_eq!(result, btype_b); + } else if btype_a == BaseType::UNKNOWN { + prop_assert_eq!(result, btype_b); + } else if btype_b == BaseType::UNKNOWN { + prop_assert_eq!(result, btype_a); + } else { + let size_a = typedesc_size(&typedesc_new(btype_a, Aggregate::SCALAR, VecSemantics::NOSEMANTICS, 0)); + let size_b = typedesc_size(&typedesc_new(btype_b, Aggregate::SCALAR, VecSemantics::NOSEMANTICS, 0)); + + if size_a < size_b { + std::mem::swap(&mut btype_a, &mut btype_b); + } + + if btype_a == BaseType::DOUBLE || btype_a == BaseType::FLOAT { + prop_assert_eq!(result, btype_a); + } else if btype_a == BaseType::UINT32 && (btype_b == BaseType::UINT16 || btype_b == BaseType::UINT8) { + prop_assert_eq!(result, btype_a); + } else if btype_a == BaseType::INT32 && (btype_b == BaseType::INT16 || btype_b == BaseType::INT8 || btype_b == BaseType::UINT16 || btype_b == BaseType::UINT8) { + prop_assert_eq!(result, btype_a); + } else if (btype_a == BaseType::UINT16 || btype_a == BaseType::HALF) && btype_b == BaseType::UINT8 { + prop_assert_eq!(result, btype_a); + } else if (btype_a == BaseType::INT16 || btype_a == BaseType::HALF) && (btype_b == BaseType::INT8 || btype_b == BaseType::UINT8) { + prop_assert_eq!(result, btype_a); + } else { + prop_assert_eq!(result, BaseType::FLOAT); + } + } + } + + #[test] + fn test_typedesc_basetype_merge_3_success( + btype_a in basetypes(), + agg_a in aggregates(), + semantics_a in vecsemantics(), + arraylen_a in i32::MIN..i32::MAX, + btype_b in basetypes(), + agg_b in aggregates(), + semantics_b in vecsemantics(), + arraylen_b in i32::MIN..i32::MAX, + mut btype_c in basetypes(), + agg_c in aggregates(), + semantics_c in vecsemantics(), + arraylen_c in i32::MIN..i32::MAX, + ) { + let typedesc_a = typedesc_new(btype_a, agg_a, semantics_a, arraylen_a); + let typedesc_b = typedesc_new(btype_b, agg_b, semantics_b, arraylen_b); + let typedesc_c = typedesc_new(btype_c, agg_c, semantics_c, arraylen_c); + let result = typedesc_basetype_merge_3(typedesc_a, typedesc_b, typedesc_c); + let mut btype_inner = typedesc_basetype_merge(typedesc_a, typedesc_b); + + if btype_inner == btype_c { + prop_assert_eq!(result, btype_inner); + prop_assert_eq!(result, btype_c); + } else if btype_inner == BaseType::UNKNOWN { + prop_assert_eq!(result, btype_c); + } else if btype_c == BaseType::UNKNOWN { + prop_assert_eq!(result, btype_inner); + } else { + let size_a = typedesc_size(&typedesc_new(btype_inner, Aggregate::SCALAR, VecSemantics::NOSEMANTICS, 0)); + let size_b = typedesc_size(&typedesc_new(btype_c, Aggregate::SCALAR, VecSemantics::NOSEMANTICS, 0)); + + if size_a < size_b { + std::mem::swap(&mut btype_inner, &mut btype_c); + } + + if btype_inner == BaseType::DOUBLE || btype_inner == BaseType::FLOAT { + prop_assert_eq!(result, btype_inner); + } else if btype_inner == BaseType::UINT32 && (btype_c == BaseType::UINT16 || btype_c == BaseType::UINT8) { + prop_assert_eq!(result, btype_inner); + } else if btype_inner == BaseType::INT32 && (btype_c == BaseType::INT16 || btype_c == BaseType::INT8 || btype_c == BaseType::UINT16 || btype_c == BaseType::UINT8) { + prop_assert_eq!(result, btype_inner); + } else if (btype_inner == BaseType::UINT16 || btype_inner == BaseType::HALF) && btype_c == BaseType::UINT8 { + prop_assert_eq!(result, btype_inner); + } else if (btype_inner == BaseType::INT16 || btype_inner == BaseType::HALF) && (btype_c == BaseType::INT8 || btype_c == BaseType::UINT8) { + prop_assert_eq!(result, btype_inner); + } else { + prop_assert_eq!(result, BaseType::FLOAT); + } + } + } + + #[test] + fn test_typedesc_convert_type_success( + value_in in i32::MIN..i32::MAX, + ) { + let mut value_out = 0.0f32.to_ne_bytes(); + let src_type = typedesc_new(BaseType::INT32, Aggregate::SCALAR, VecSemantics::NOSEMANTICS, 0); + let dst_type = typedesc_new(BaseType::FLOAT, Aggregate::SCALAR, VecSemantics::NOSEMANTICS, 0); + + prop_assert!(typedesc_convert_type(src_type, &value_in.to_ne_bytes(), dst_type, &mut value_out, 1)); + prop_assert_eq!(f32::from_ne_bytes(value_out), value_in as f32); + } + } +} diff --git a/src/rust/src/lib.rs b/src/rust/src/lib.rs new file mode 100644 index 0000000000..ebe96d539d --- /dev/null +++ b/src/rust/src/lib.rs @@ -0,0 +1,8 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + +/// Low-level oiio-sys FFI bindings to OpenImageIO. +use oiio_sys as sys; + +pub mod typedesc; diff --git a/src/rust/src/typedesc.rs b/src/rust/src/typedesc.rs new file mode 100644 index 0000000000..56ae938bc1 --- /dev/null +++ b/src/rust/src/typedesc.rs @@ -0,0 +1,1315 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + +use crate::sys; + +/// BaseType is a simple enum describing the base data types that +/// correspond (mostly) to the C/C++ built-in types. +#[derive(Debug, Clone, Copy, Default)] +#[cfg_attr(test, derive(proptest_derive::Arbitrary))] +pub enum BaseType { + #[default] + /// unknown type + Unknown, + /// void/no type + None, + /// 8-bit unsigned int values ranging from 0..255, + /// (C/C++ `unsigned char`). + UInt8, + /// 8-bit int values ranging from -128..127, + /// (C/C++ `char`). + Int8, + /// 16-bit int values ranging from 0..65535, + /// (C/C++ `unsigned short`). + UInt16, + /// 16-bit int values ranging from -32768..32767, + /// (C/C++ `short`). + Int16, + /// 32-bit unsigned int values (C/C++ `unsigned int`). + UInt32, + /// signed 32-bit int values (C/C++ `int`). + Int32, + /// 64-bit unsigned int values (C/C++ + /// `unsigned long long` on most architectures). + UInt64, + /// signed 64-bit int values (C/C++ `long long` + /// on most architectures). + Int64, + /// 16-bit IEEE floating point values (OpenEXR `half`). + Half, + /// 32-bit IEEE floating point values, (C/C++ `float`). + Float, + /// 64-bit IEEE floating point values, (C/C++ `double`). + Double, + /// Character string. + String, + /// A pointer value. + Ptr, + /// A uint64 that is the hash of a ustring. + UStringHash, + LastBase, +} + +impl PartialEq for BaseType { + fn eq(&self, other: &Self) -> bool { + sys::typedesc::BaseType::from(*self) == sys::typedesc::BaseType::from(*other) + } +} + +impl Eq for BaseType {} + +impl PartialOrd for BaseType { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for BaseType { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + sys::typedesc::BaseType::from(*self).cmp(&sys::typedesc::BaseType::from(*other)) + } +} + +impl PartialEq for BaseType { + /// Compare a TypeDesc to a basetype (it's the same if it has the + /// same base type and is not an aggregate or an array). + fn eq(&self, other: &TypeDesc) -> bool { + sys::typedesc::basetype_eq_typedesc((*self).into(), &(*other).into()) + } + + /// Compare a TypeDesc to a basetype (it's the same if it has the + /// same base type and is not an aggregate or an array). + fn ne(&self, other: &TypeDesc) -> bool { + sys::typedesc::basetype_ne_typedesc((*self).into(), &(*other).into()) + } +} + +impl From for sys::typedesc::BaseType { + fn from(value: BaseType) -> Self { + match value { + BaseType::Unknown => Self::UNKNOWN, + BaseType::None => Self::NONE, + BaseType::UInt8 => Self::UINT8, + BaseType::Int8 => Self::INT8, + BaseType::UInt16 => Self::UINT16, + BaseType::Int16 => Self::INT16, + BaseType::UInt32 => Self::UINT32, + BaseType::Int32 => Self::INT32, + BaseType::UInt64 => Self::UINT64, + BaseType::Int64 => Self::INT64, + BaseType::Half => Self::HALF, + BaseType::Float => Self::FLOAT, + BaseType::Double => Self::DOUBLE, + BaseType::String => Self::STRING, + BaseType::Ptr => Self::PTR, + BaseType::UStringHash => Self::USTRINGHASH, + BaseType::LastBase => Self::LASTBASE, + } + } +} + +impl From for BaseType { + fn from(value: sys::typedesc::BaseType) -> Self { + match value { + sys::typedesc::BaseType::UNKNOWN => Self::Unknown, + sys::typedesc::BaseType::NONE => Self::None, + sys::typedesc::BaseType::UINT8 => Self::UInt8, + sys::typedesc::BaseType::INT8 => Self::Int8, + sys::typedesc::BaseType::UINT16 => Self::UInt16, + sys::typedesc::BaseType::INT16 => Self::Int16, + sys::typedesc::BaseType::UINT32 => Self::UInt32, + sys::typedesc::BaseType::INT32 => Self::Int32, + sys::typedesc::BaseType::UINT64 => Self::UInt64, + sys::typedesc::BaseType::INT64 => Self::Int64, + sys::typedesc::BaseType::HALF => Self::Half, + sys::typedesc::BaseType::FLOAT => Self::Float, + sys::typedesc::BaseType::DOUBLE => Self::Double, + sys::typedesc::BaseType::STRING => Self::String, + sys::typedesc::BaseType::PTR => Self::Ptr, + sys::typedesc::BaseType::USTRINGHASH => Self::UStringHash, + sys::typedesc::BaseType::LASTBASE => Self::LastBase, + _ => unreachable!(), + } + } +} + +impl From for u32 { + fn from(value: BaseType) -> Self { + sys::typedesc::BaseType::from(value).repr + } +} + +/// Aggregate describes whether our TypeDesc is a simple scalar of one +/// of the BaseType's, or one of several simple aggregates. +/// +/// Note that aggregates and arrays are different. A `TypeDesc(FLOAT,3)` +/// is an array of three floats, a `TypeDesc(FLOAT,VEC3)` is a single +/// 3-component vector comprised of floats, and `TypeDesc(FLOAT,3,VEC3)` +/// is an array of 3 vectors, each of which is comprised of 3 floats. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(test, derive(proptest_derive::Arbitrary))] +pub enum Aggregate { + /// A single scalar value (such as a raw `int` or + /// `float` in C). This is the default. + Scalar, + /// 2 values representing a 2D vector. + Vec2, + /// 3 values representing a 3D vector. + Vec3, + /// 4 values representing a 4D vector. + Vec4, + /// 9 values representing a 3x3 matrix. + Matrix33, + /// 16 values representing a 4x4 matrix. + Matrix44, +} + +impl PartialEq for Aggregate { + fn eq(&self, other: &Self) -> bool { + sys::typedesc::Aggregate::from(*self) == sys::typedesc::Aggregate::from(*other) + } +} + +impl Eq for Aggregate {} + +impl PartialOrd for Aggregate { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Aggregate { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + sys::typedesc::Aggregate::from(*self).cmp(&sys::typedesc::Aggregate::from(*other)) + } +} + +impl From for sys::typedesc::Aggregate { + fn from(value: Aggregate) -> Self { + match value { + Aggregate::Scalar => Self::SCALAR, + Aggregate::Vec2 => Self::VEC2, + Aggregate::Vec3 => Self::VEC3, + Aggregate::Vec4 => Self::VEC4, + Aggregate::Matrix33 => Self::MATRIX33, + Aggregate::Matrix44 => Self::MATRIX44, + } + } +} + +impl From for Aggregate { + fn from(value: sys::typedesc::Aggregate) -> Self { + match value { + sys::typedesc::Aggregate::SCALAR => Self::Scalar, + sys::typedesc::Aggregate::VEC2 => Self::Vec2, + sys::typedesc::Aggregate::VEC3 => Self::Vec3, + sys::typedesc::Aggregate::VEC4 => Self::Vec4, + sys::typedesc::Aggregate::MATRIX33 => Self::Matrix33, + sys::typedesc::Aggregate::MATRIX44 => Self::Matrix44, + _ => unreachable!(), + } + } +} + +impl From for u32 { + fn from(value: Aggregate) -> Self { + sys::typedesc::Aggregate::from(value).repr + } +} + +/// VecSemantics gives hints about what the data represent (for example, +/// if a spatial vector quantity should transform as a point, direction +/// vector, or surface normal). +#[derive(Debug, Clone, Copy, Default)] +#[cfg_attr(test, derive(proptest_derive::Arbitrary))] +pub enum VecSemantics { + #[default] + /// No semantic hints. + NoSemantics, + /// Color + Color, + /// Point: a spatial location + Point, + /// Vector: a spatial direction + Vector, + /// Normal: a surface normal + Normal, + /// indicates an `int[2]` representing the standard + /// 4-byte encoding of an SMPTE timecode. + Timecode, + /// indicates an `int[7]` representing the standard + /// 28-byte encoding of an SMPTE keycode. + Keycode, + /// A `Vec2` representing a rational number `val[0] / val[1]` + Rational, + /// A `Vec2[2]` or `Vec3[2]` that represents a 2D or 3D bounds (min/max) + Box, +} + +impl PartialEq for VecSemantics { + fn eq(&self, other: &Self) -> bool { + sys::typedesc::VecSemantics::from(*self) == sys::typedesc::VecSemantics::from(*other) + } +} + +impl Eq for VecSemantics {} + +impl PartialOrd for VecSemantics { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for VecSemantics { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + sys::typedesc::VecSemantics::from(*self).cmp(&sys::typedesc::VecSemantics::from(*other)) + } +} + +impl From for sys::typedesc::VecSemantics { + fn from(value: VecSemantics) -> Self { + match value { + VecSemantics::NoSemantics => Self::NOSEMANTICS, + VecSemantics::Color => Self::COLOR, + VecSemantics::Point => Self::POINT, + VecSemantics::Vector => Self::VECTOR, + VecSemantics::Normal => Self::NORMAL, + VecSemantics::Timecode => Self::TIMECODE, + VecSemantics::Keycode => Self::KEYCODE, + VecSemantics::Rational => Self::RATIONAL, + VecSemantics::Box => Self::BOX, + } + } +} + +impl From for VecSemantics { + fn from(value: sys::typedesc::VecSemantics) -> Self { + match value { + sys::typedesc::VecSemantics::NOSEMANTICS => Self::NoSemantics, + sys::typedesc::VecSemantics::COLOR => Self::Color, + sys::typedesc::VecSemantics::POINT => Self::Point, + sys::typedesc::VecSemantics::VECTOR => Self::Vector, + sys::typedesc::VecSemantics::NORMAL => Self::Normal, + sys::typedesc::VecSemantics::TIMECODE => Self::Timecode, + sys::typedesc::VecSemantics::KEYCODE => Self::Keycode, + sys::typedesc::VecSemantics::RATIONAL => Self::Rational, + sys::typedesc::VecSemantics::BOX => Self::Box, + _ => unreachable!(), + } + } +} + +impl From for u32 { + fn from(value: VecSemantics) -> Self { + sys::typedesc::VecSemantics::from(value).repr + } +} + +/// A TypeDesc describes simple data types. +/// +/// It frequently comes up (in my experience, with renderers and image +/// handling programs) that you want a way to describe data that is passed +/// through APIs through blind pointers. These are some simple classes +/// that provide a simple type descriptor system. This is not meant to +/// be comprehensive -- for example, there is no provision for structs, +/// unions, typed pointers, const, or 'nested' type definitions. Just simple +/// integer and floating point, *common* aggregates such as 3-points, and +/// reasonably-lengthed arrays thereof. +#[derive(Debug, Clone, Copy)] +#[cfg_attr(test, derive(proptest_derive::Arbitrary))] +pub struct TypeDesc { + /// C data type at the heart of our type + pub basetype: BaseType, + /// What kind of AGGREGATE is it? + pub aggregate: Aggregate, + /// Hint: What does the aggregate represent? + pub vecsemantics: VecSemantics, + /// Array length, 0 = not array, -1 = unsized + pub arraylen: i32, +} + +/// Display the name, for printing and whatnot. For example, +/// `float`, `int[5]`, `normal` +impl std::fmt::Display for TypeDesc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let inner = (*self).into(); + f.write_str(sys::typedesc::typedesc_as_str(&inner)) + } +} + +impl From for sys::typedesc::TypeDesc { + fn from(value: TypeDesc) -> Self { + sys::typedesc::typedesc_new( + value.basetype.into(), + value.aggregate.into(), + value.vecsemantics.into(), + value.arraylen, + ) + } +} + +/// Construct from a string (e.g., `float[3]`). If no valid +/// type could be assembled, set base to `UNKNOWN`. +/// +/// Examples: +/// +/// ``` +/// # use oiio::typedesc::*; +/// assert_eq!(TypeDesc::from("int"), TypeDesc::new(BaseType::Int32, Aggregate::Scalar, VecSemantics::NoSemantics, 0)); +/// assert_eq!(TypeDesc::from("float"), TypeDesc::new(BaseType::Float, Aggregate::Scalar, VecSemantics::NoSemantics, 0)); +/// assert_eq!(TypeDesc::from("uint16"), TypeDesc::new(BaseType::UInt16, Aggregate::Scalar, VecSemantics::NoSemantics, 0)); +/// assert_eq!(TypeDesc::from("point"), TypeDesc::new(BaseType::Float, Aggregate::Vec3, VecSemantics::Point, 0)); +/// ``` +/// +impl From<&str> for TypeDesc { + fn from(value: &str) -> Self { + sys::typedesc::typedesc_from_string(value).into() + } +} + +/// Construct from a string (e.g., `float[3]`). If no valid +/// type could be assembled, set base to `UNKNOWN`. +/// +/// Examples: +/// +/// ``` +/// # use oiio::typedesc::*; +/// assert_eq!(TypeDesc::from("int".to_string()), TypeDesc::new(BaseType::Int32, Aggregate::Scalar, VecSemantics::NoSemantics, 0)); +/// assert_eq!(TypeDesc::from("float".to_string()), TypeDesc::new(BaseType::Float, Aggregate::Scalar, VecSemantics::NoSemantics, 0)); +/// assert_eq!(TypeDesc::from("uint16".to_string()), TypeDesc::new(BaseType::UInt16, Aggregate::Scalar, VecSemantics::NoSemantics, 0)); +/// assert_eq!(TypeDesc::from("point".to_string()), TypeDesc::new(BaseType::Float, Aggregate::Vec3, VecSemantics::Point, 0)); +/// ``` +/// +impl From for TypeDesc { + fn from(value: String) -> Self { + sys::typedesc::typedesc_from_string(&value).into() + } +} + +/// Construct from a string (e.g., `float[3]`). If no valid +/// type could be assembled, set base to `UNKNOWN`. +/// +/// Examples: +/// +/// ``` +/// # use oiio::typedesc::*; +/// assert_eq!(TypeDesc::from(&"int".to_string()), TypeDesc::new(BaseType::Int32, Aggregate::Scalar, VecSemantics::NoSemantics, 0)); +/// assert_eq!(TypeDesc::from(&"float".to_string()), TypeDesc::new(BaseType::Float, Aggregate::Scalar, VecSemantics::NoSemantics, 0)); +/// assert_eq!(TypeDesc::from(&"uint16".to_string()), TypeDesc::new(BaseType::UInt16, Aggregate::Scalar, VecSemantics::NoSemantics, 0)); +/// assert_eq!(TypeDesc::from(&"point".to_string()), TypeDesc::new(BaseType::Float, Aggregate::Vec3, VecSemantics::Point, 0)); +/// ``` +/// +impl From<&String> for TypeDesc { + fn from(value: &String) -> Self { + sys::typedesc::typedesc_from_string(&value).into() + } +} + +impl From for TypeDesc { + fn from(value: sys::typedesc::TypeDesc) -> Self { + Self { + basetype: sys::typedesc::BaseType { + repr: value.basetype as u32, + } + .into(), + aggregate: sys::typedesc::Aggregate { + repr: value.aggregate as u32, + } + .into(), + vecsemantics: sys::typedesc::VecSemantics { + repr: value.vecsemantics as u32, + } + .into(), + arraylen: value.arraylen, + } + } +} + +impl PartialEq for TypeDesc { + /// Compare two TypeDesc values for equality. + fn eq(&self, other: &Self) -> bool { + sys::typedesc::typedesc_eq(&(*self).into(), &(*other).into()) + } + + /// Compare two TypeDesc values for inequality. + fn ne(&self, other: &Self) -> bool { + sys::typedesc::typedesc_ne(&(*self).into(), &(*other).into()) + } +} + +impl PartialEq for TypeDesc { + /// Compare a TypeDesc to a basetype (it's the same if it has the + /// same base type and is not an aggregate or an array). + fn eq(&self, other: &BaseType) -> bool { + sys::typedesc::typedesc_eq_basetype(&(*self).into(), (*other).into()) + } + + /// Compare a TypeDesc to a basetype (it's the same if it has the + /// same base type and is not an aggregate or an array). + fn ne(&self, other: &BaseType) -> bool { + sys::typedesc::typedesc_ne_basetype(&(*self).into(), (*other).into()) + } +} + +impl Eq for TypeDesc {} + +impl PartialOrd for TypeDesc { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for TypeDesc { + /// Test for lexicographic ordering, comes in handy for lots of containers + /// and algorithms. + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + if self == other { + core::cmp::Ordering::Equal + } else if sys::typedesc::typedesc_lt(&(*self).into(), &(*other).into()) { + core::cmp::Ordering::Less + } else { + core::cmp::Ordering::Greater + } + } +} + +impl TypeDesc { + /// Construct from a BaseType and optional aggregateness, semantics, + /// and arrayness. + pub fn new(btype: BaseType, agg: Aggregate, semantics: VecSemantics, arraylen: i32) -> Self { + sys::typedesc::typedesc_new(btype.into(), agg.into(), semantics.into(), arraylen).into() + } + + /// Construct an array of a non-aggregate BaseType. + pub fn from_basetype_arraylen(btype: BaseType, arraylen: i32) -> Self { + sys::typedesc::typedesc_from_basetype_arraylen(btype.into(), arraylen).into() + } + + /// Construct an array from BaseType, Aggregate, and array length, + /// with unspecified (or moot) semantic hints. + pub fn from_basetype_aggregate_arraylen( + btype: BaseType, + agg: Aggregate, + arraylen: i32, + ) -> Self { + sys::typedesc::typedesc_from_basetype_aggregate_arraylen(btype.into(), agg.into(), arraylen) + .into() + } + + /// Return the number of elements: 1 if not an array, or the array + /// length. Invalid to call this for arrays of undetermined size. + pub fn numelements(&self) -> usize { + sys::typedesc::typedesc_numelements(&(*self).into()) + } + + /// Return the number of basetype values: the aggregate count multiplied + /// by the array length (or 1 if not an array). Invalid to call this + /// for arrays of undetermined size. + pub fn basevalues(&self) -> usize { + sys::typedesc::typedesc_basevalues(&(*self).into()) + } + + /// Does this TypeDesc describe an array? + pub fn is_array(&self) -> bool { + sys::typedesc::typedesc_is_array(&(*self).into()) + } + + /// Does this TypeDesc describe an array, but whose length is not + /// specified? + pub fn is_unsized_array(&self) -> bool { + sys::typedesc::typedesc_is_unsized_array(&(*self).into()) + } + + /// Does this TypeDesc describe an array, whose length is specified? + pub fn is_sized_array(&self) -> bool { + sys::typedesc::typedesc_is_sized_array(&(*self).into()) + } + + /// Return the size, in bytes, of this type. + pub fn size(&self) -> usize { + sys::typedesc::typedesc_size(&(*self).into()) + } + + /// Return the type of one element, i.e., strip out the array-ness. + pub fn elementtype(&self) -> Self { + sys::typedesc::typedesc_elementtype(&(*self).into()).into() + } + + /// Return the size, in bytes, of one element of this type (that is, + /// ignoring whether it's an array). + pub fn elementsize(&self) -> usize { + sys::typedesc::typedesc_elementsize(&(*self).into()) + } + + /// Return just the underlying C scalar type, i.e., strip out the + /// array-ness and the aggregateness. + pub fn scalartype(&self) -> Self { + sys::typedesc::typedesc_scalartype(&(*self).into()).into() + } + + /// Return the base type size, i.e., stripped of both array-ness + /// and aggregateness. + pub fn basesize(&self) -> usize { + sys::typedesc::typedesc_basesize(&(*self).into()) + } + + /// True if it's a floating-point type (versus a fundamentally + /// integral type or something else like a string). + pub fn is_floating_point(&self) -> bool { + sys::typedesc::typedesc_is_floating_point(&(*self).into()) + } + + /// True if it's a signed type that allows for negative values. + pub fn is_signed(&self) -> bool { + sys::typedesc::typedesc_is_signed(&(*self).into()) + } + + /// Shortcut: is it Unknown? + pub fn is_unknown(&self) -> bool { + sys::typedesc::typedesc_is_unknown(&(*self).into()) + } + + /// Set `typedesc` to the type described in the string. Return the + /// length of the part of the string that describes the type. If + /// no valid type could be assembled, return 0 and do not modify + /// `typedesc`. + pub fn set_from_string(&mut self, typestring: &str) -> usize { + let mut typedesc = sys::typedesc::TypeDesc::from(*self); + let result = sys::typedesc::typedesc_fromstring(&mut typedesc, typestring); + + *self = typedesc.into(); + + result + } + + /// TypeDesc's are equivalent if they are equal, or if their only + /// inequality is differing vector semantics. + pub fn equivalent(&self, b: &TypeDesc) -> bool { + sys::typedesc::typedesc_equivalent(&(*self).into(), &(*b).into()) + } + + /// Is this a 2-vector aggregate (of the given type)? + pub fn is_vec2(&self, b: BaseType) -> bool { + sys::typedesc::typedesc_is_vec2(&(*self).into(), b.into()) + } + + /// Is this a 3-vector aggregate (of the given type)? + pub fn is_vec3(&self, b: BaseType) -> bool { + sys::typedesc::typedesc_is_vec3(&(*self).into(), b.into()) + } + + /// Is this a 4-vector aggregate (of the given type)? + pub fn is_vec4(&self, b: BaseType) -> bool { + sys::typedesc::typedesc_is_vec4(&(*self).into(), b.into()) + } + + /// Is this an array of aggregates that represents a 2D bounding box? + pub fn is_box2(&self, b: BaseType) -> bool { + sys::typedesc::typedesc_is_box2(&(*self).into(), b.into()) + } + + /// Is this an array of aggregates that represents a 3D bounding box? + pub fn is_box3(&self, b: BaseType) -> bool { + sys::typedesc::typedesc_is_box3(&(*self).into(), b.into()) + } + + /// Demote the type to a non-array + pub fn unarray(&mut self) -> () { + let mut typedesc = sys::typedesc::TypeDesc::from(*self); + sys::typedesc::typedesc_unarray(&mut typedesc); + *self = typedesc.into(); + } + + /// Given base data types of a and b, return a basetype that is a best + /// guess for one that can handle both without any loss of range or + /// precision. + pub fn basetype_merge(a: TypeDesc, b: TypeDesc) -> BaseType { + sys::typedesc::typedesc_basetype_merge(a.into(), b.into()).into() + } + + /// Given base data types of a and b, return a basetype that is a best + /// guess for one that can handle both without any loss of range or + /// precision. + pub fn basetype_merge_3(a: TypeDesc, b: TypeDesc, c: TypeDesc) -> BaseType { + { + sys::typedesc::typedesc_basetype_merge_3(a.into(), b.into(), c.into()).into() + } + } + + /// Given data pointed to by src and described by srctype, copy it to the + /// memory pointed to by dst and described by dsttype, and return true if a + /// conversion is possible, false if it is not. If the types are equivalent, + /// this is a straightforward memory copy. If the types differ, there are + /// several non-equivalent type conversions that will nonetheless succeed: + /// * If dsttype is a string (and therefore dst points to a ustring or a + /// `char*`): it will always succeed, producing a string akin to calling + /// `typedesc.to_string()`. + /// * If dsttype is int32 or uint32: other integer types will do their best + /// (caveat emptor if you mix signed/unsigned). Also a source string will + /// convert to int if and only if its characters form a valid integer. + /// * If dsttype is float: inteegers and other float types will do + /// their best conversion; strings will convert if and only if their + /// characters form a valid float number. + pub fn convert_type( + srctype: TypeDesc, + src: &[u8], + dsttype: TypeDesc, + dst: &mut [u8], + n: i32, + ) -> bool { + sys::typedesc::typedesc_convert_type(srctype.into(), src, dsttype.into(), dst, n) + } +} + +#[cfg(test)] +mod tests { + use proptest::prelude::*; + + use super::*; + + fn from_string_strategy() -> impl Strategy { + any::().prop_filter_map("BaseType::LastBase is unsupported", |base_type| { + let base_type_str = match base_type { + BaseType::Unknown => "unknown", + BaseType::None => "void", + BaseType::UInt8 => "uint8", + BaseType::Int8 => "int8", + BaseType::UInt16 => "uint16", + BaseType::Int16 => "int16", + BaseType::UInt32 => "uint", + BaseType::Int32 => "int", + BaseType::UInt64 => "uint64", + BaseType::Int64 => "int64", + BaseType::Half => "half", + BaseType::Float => "float", + BaseType::Double => "double", + BaseType::String => "string", + BaseType::Ptr => "pointer", + BaseType::UStringHash => "ustringhash", + BaseType::LastBase => return None, + }; + + Some(( + TypeDesc::from_basetype_arraylen(base_type, 0), + base_type_str, + )) + }) + } + + proptest! { + #[test] + fn test_typedesc_new_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in i32::MIN..i32::MAX, + ) { + let result = TypeDesc::new(btype, agg, semantics, arraylen); + + prop_assert_eq!(result.basetype, btype); + prop_assert_eq!(result.aggregate, agg); + prop_assert_eq!(result.vecsemantics, semantics); + prop_assert_eq!(result.arraylen, arraylen); + } + + #[test] + fn test_typedesc_from_basetype_arraylen_success( + btype in any::(), + arraylen in i32::MIN..i32::MAX, + ) { + let result = TypeDesc::from_basetype_arraylen(btype, arraylen); + + prop_assert_eq!(result.basetype, btype); + prop_assert_eq!(result.aggregate, Aggregate::Scalar); + prop_assert_eq!(result.vecsemantics, VecSemantics::NoSemantics); + prop_assert_eq!(result.arraylen, arraylen); + } + + #[test] + fn test_typedesc_from_basetype_aggregate_arraylen_success( + btype in any::(), + agg in any::(), + arraylen in any::() + ) { + let result = TypeDesc::from_basetype_aggregate_arraylen(btype, agg, arraylen); + + prop_assert_eq!(result.basetype, btype); + prop_assert_eq!(result.aggregate, agg); + prop_assert_eq!(result.vecsemantics, VecSemantics::NoSemantics); + prop_assert_eq!(result.arraylen, arraylen); + } + + #[test] + fn test_typedesc_from_string_success( + (expected_type_desc, input_str) in from_string_strategy() + ) { + let result = TypeDesc::from(input_str); + + prop_assert_eq!(result.basetype, expected_type_desc.basetype); + prop_assert_eq!(result.aggregate, expected_type_desc.aggregate); + prop_assert_eq!(result.vecsemantics, expected_type_desc.vecsemantics); + prop_assert_eq!(result.arraylen, expected_type_desc.arraylen); + } + + + #[test] + fn test_typedesc_clone_success( + type_desc in any::(), + ) { + let result = type_desc.clone(); + + prop_assert_eq!(result.basetype, type_desc.basetype); + prop_assert_eq!(result.aggregate, type_desc.aggregate); + prop_assert_eq!(result.vecsemantics, type_desc.vecsemantics); + prop_assert_eq!(result.arraylen, type_desc.arraylen); + } + + #[test] + fn test_typedesc_as_str_success( + (input_type_desc, expected_str) in from_string_strategy() + ) { + let result = input_type_desc.to_string(); + + prop_assert_eq!(result, expected_str); + } + + #[test] + fn test_typedesc_numelements_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in 1..i32::MAX + ) { + let typedesc = TypeDesc::new(btype, agg, semantics, arraylen); + let result = typedesc.numelements(); + + prop_assert_eq!(result, arraylen as usize); + } + + #[test] + fn test_typedesc_basevalues_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in 1..i32::MAX + ) { + let typedesc = TypeDesc::new(btype, agg, semantics, arraylen); + let result = typedesc.basevalues(); + + prop_assert_eq!(result, arraylen as usize * u32::from(agg) as usize); + } + + #[test] + fn test_typedesc_is_array_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in -2..2 + ) { + let typedesc = TypeDesc::new(btype, agg, semantics, arraylen); + let result = typedesc.is_array(); + + prop_assert_eq!(result, arraylen != 0); + } + + #[test] + fn test_typedesc_is_unsized_array_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in -2..2 + ) { + let typedesc = TypeDesc::new(btype, agg, semantics, arraylen); + let result = typedesc.is_unsized_array(); + + prop_assert_eq!(result, arraylen < 0); + } + + #[test] + fn test_typedesc_is_sized_array_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in -2..2 + ) { + let typedesc = TypeDesc::new(btype, agg, semantics, arraylen); + let result = typedesc.is_sized_array(); + + prop_assert_eq!(result, arraylen > 0); + } + + #[test] + fn test_typedesc_size_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in 1..2 + ) { + let typedesc = TypeDesc::new(btype, agg, semantics, arraylen); + let result = typedesc.size(); + + prop_assert_eq!(result, arraylen as usize * typedesc.elementsize()); + } + + #[test] + fn test_typedesc_elementtype_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in i32::MIN..i32::MAX + ) { + let typedesc = TypeDesc::new(btype, agg, semantics, arraylen); + let result = typedesc.elementtype(); + + prop_assert_eq!(result.basetype, btype); + prop_assert_eq!(result.aggregate, agg); + prop_assert_eq!(result.vecsemantics, semantics); + prop_assert_eq!(result.arraylen, 0); + } + + #[test] + fn test_typedesc_elementsize_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in i32::MIN..i32::MAX + ) { + let typedesc = TypeDesc::new(btype, agg, semantics, arraylen); + let result = typedesc.elementsize(); + + prop_assert_eq!(result, u32::from(agg) as usize * typedesc.basesize()); + } + + #[test] + fn test_typedesc_scalartype_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in i32::MIN..i32::MAX + ) { + let typedesc = TypeDesc::new(btype, agg, semantics, arraylen); + let result = typedesc.scalartype(); + + prop_assert_eq!(result.basetype, btype); + prop_assert_eq!(result.aggregate, Aggregate::Scalar); + prop_assert_eq!(result.vecsemantics, VecSemantics::NoSemantics); + prop_assert_eq!(result.arraylen, 0); + } + + #[test] + fn test_typedesc_basesize_success( + btype in any::().prop_filter("BaseType::LastBase not supported", |btype| !matches!(*btype, crate::typedesc::BaseType::LastBase)), + agg in any::(), + semantics in any::(), + arraylen in i32::MIN..i32::MAX + ) { + let typedesc = TypeDesc::new(btype, agg, semantics, arraylen); + let result = typedesc.basesize(); + + let expected = match btype { + BaseType::Unknown => 0, + BaseType::None => 0, + BaseType::UInt8 => size_of::(), + BaseType::Int8 => size_of::(), + BaseType::UInt16 => size_of::(), + BaseType::Int16 => size_of::(), + BaseType::UInt32 => size_of::(), + BaseType::Int32 => size_of::(), + BaseType::UInt64 => size_of::(), + BaseType::Int64 => size_of::(), + BaseType::Half => size_of::() / 2, + BaseType::Float => size_of::(), + BaseType::Double => size_of::(), + BaseType::String => size_of::<*const std::ffi::c_char>(), + BaseType::Ptr => size_of::<*const std::ffi::c_char>(), + BaseType::UStringHash => 8, // TODO: Get the size of the UStringHash type. + BaseType::LastBase => unreachable!(), + }; + + prop_assert_eq!(result, expected); + } + + #[test] + fn test_typedesc_is_floating_point_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in i32::MIN..i32::MAX + ) { + let typedesc = TypeDesc::new(btype, agg, semantics, arraylen); + let result = typedesc.is_floating_point(); + + prop_assert_eq!(result, matches!(btype, BaseType::Half | BaseType::Float | BaseType::Double)); + } + + #[test] + fn test_typedesc_is_unknown_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in i32::MIN..i32::MAX + ) { + let typedesc = TypeDesc::new(btype, agg, semantics, arraylen); + let result = typedesc.is_unknown(); + + prop_assert_eq!(result, matches!(btype, BaseType::Unknown)); + } + + #[test] + fn test_typedesc_fromstring_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in i32::MIN..i32::MAX, + (expected_type_desc, input_str) in from_string_strategy() + ) { + let mut typedesc = TypeDesc::new(btype, agg, semantics, arraylen); + typedesc.set_from_string(&input_str); + + prop_assert_eq!(typedesc.basetype, expected_type_desc.basetype); + prop_assert_eq!(typedesc.aggregate, expected_type_desc.aggregate); + prop_assert_eq!(typedesc.vecsemantics, expected_type_desc.vecsemantics); + prop_assert_eq!(typedesc.arraylen, expected_type_desc.arraylen); + } + + #[test] + fn test_typedesc_eq_success( + btype_a in any::(), + agg_a in any::(), + semantics_a in any::(), + arraylen_a in i32::MIN..i32::MAX, + btype_b in any::(), + agg_b in any::(), + semantics_b in any::(), + arraylen_b in i32::MIN..i32::MAX, + ) { + let typedesc_a = TypeDesc::new(btype_a, agg_a, semantics_a, arraylen_a); + let typedesc_b = TypeDesc::new(btype_b, agg_b, semantics_b, arraylen_b); + + if btype_a == btype_b && agg_a == agg_b && semantics_a == semantics_b && arraylen_a == arraylen_b { + prop_assert!(typedesc_a == typedesc_b); + } else { + prop_assert!(!(typedesc_a == typedesc_b)); + } + } + + #[test] + fn test_typedesc_ne_success( + btype_a in any::(), + agg_a in any::(), + semantics_a in any::(), + arraylen_a in i32::MIN..i32::MAX, + btype_b in any::(), + agg_b in any::(), + semantics_b in any::(), + arraylen_b in i32::MIN..i32::MAX, + ) { + let typedesc_a = TypeDesc::new(btype_a, agg_a, semantics_a, arraylen_a); + let typedesc_b = TypeDesc::new(btype_b, agg_b, semantics_b, arraylen_b); + + if btype_a == btype_b && agg_a == agg_b && semantics_a == semantics_b && arraylen_a == arraylen_b { + prop_assert!(!(typedesc_a != typedesc_b)); + } else { + prop_assert!(typedesc_a != typedesc_b); + } + } + + #[test] + fn test_typedesc_eq_basetype_success( + btype_a in any::(), + agg_a in any::(), + semantics_a in any::(), + arraylen_a in -2..2, + btype_b in any::(), + ) { + let typedesc_a = TypeDesc::new(btype_a, agg_a, semantics_a, arraylen_a); + + if btype_a == btype_b && agg_a == Aggregate::Scalar && !typedesc_a.is_array() { + prop_assert!(typedesc_a == btype_b); + prop_assert!(btype_b == typedesc_a); + } else { + prop_assert!(!(typedesc_a == btype_b)); + prop_assert!(!(btype_b == typedesc_a)); + } + } + + #[test] + fn test_typedesc_ne_basetype_success( + btype_a in any::(), + agg_a in any::(), + semantics_a in any::(), + arraylen_a in -2..2, + btype_b in any::(), + ) { + let typedesc_a = TypeDesc::new(btype_a, agg_a, semantics_a, arraylen_a); + + if btype_a == btype_b && agg_a == Aggregate::Scalar && !typedesc_a.is_array() { + prop_assert!(!(typedesc_a != btype_b)); + prop_assert!(!(btype_b != typedesc_a)); + } else { + prop_assert!(typedesc_a != btype_b); + prop_assert!(btype_b != typedesc_a); + } + } + + #[test] + fn test_typedesc_equivalent_success( + btype_a in any::(), + agg_a in any::(), + semantics_a in any::(), + btype_b in any::(), + agg_b in any::(), + semantics_b in any::(), + ) { + let typedesc_a = TypeDesc::new(btype_a, agg_a, semantics_a, 0); + let typedesc_b = TypeDesc::new(btype_b, agg_b, semantics_b, 0); + + if btype_a == btype_b && agg_a == agg_b { + prop_assert!(typedesc_a.equivalent(&typedesc_b)); + } else { + prop_assert!(!typedesc_a.equivalent(&typedesc_b)); + } + } + + #[test] + fn test_typedesc_is_vec2_success( + btype in any::(), + btype_b in any::(), + agg in any::(), + semantics in any::(), + arraylen in -2..2 + ) { + let result = TypeDesc::new(btype, agg, semantics, arraylen); + + if result.aggregate == Aggregate::Vec2 && result.basetype == btype_b && !result.is_array() { + prop_assert!(result.is_vec2(btype_b)); + } else { + prop_assert!(!result.is_vec2(btype_b)); + + } + } + + #[test] + fn test_typedesc_is_vec3_success( + btype in any::(), + btype_b in any::(), + agg in any::(), + semantics in any::(), + arraylen in -2..2 + ) { + let result = TypeDesc::new(btype, agg, semantics, arraylen); + + if result.aggregate == Aggregate::Vec3 && result.basetype == btype_b && !result.is_array() { + prop_assert!(result.is_vec3(btype_b)); + } else { + prop_assert!(!result.is_vec3(btype_b)); + + } + } + + #[test] + fn test_typedesc_is_vec4_success( + btype in any::(), + btype_b in any::(), + agg in any::(), + semantics in any::(), + arraylen in -2..2 + ) { + let result = TypeDesc::new(btype, agg, semantics, arraylen); + + if result.aggregate == Aggregate::Vec4 && result.basetype == btype_b && !result.is_array() { + prop_assert!(result.is_vec4(btype_b)); + } else { + prop_assert!(!result.is_vec4(btype_b)); + + } + } + + #[test] + fn test_typedesc_is_box2_success( + btype in any::(), + btype_b in any::(), + agg in any::(), + semantics in any::(), + arraylen in -2..4 + ) { + let result = TypeDesc::new(btype, agg, semantics, arraylen); + + if result.aggregate == Aggregate::Vec2 && result.vecsemantics == VecSemantics::Box && result.basetype == btype_b && arraylen == 2 { + prop_assert!(result.is_box2(btype_b)); + } else { + prop_assert!(!result.is_box2(btype_b)); + + } + } + + #[test] + fn test_typedesc_is_box3_success( + btype in any::(), + btype_b in any::(), + agg in any::(), + semantics in any::(), + arraylen in -2..4 + ) { + let result = TypeDesc::new(btype, agg, semantics, arraylen); + + if result.aggregate == Aggregate::Vec3 && result.vecsemantics == VecSemantics::Box && result.basetype == btype_b && arraylen == 2 { + prop_assert!(result.is_box3(btype_b)); + } else { + prop_assert!(!result.is_box3(btype_b)); + + } + } + + #[test] + fn test_typedesc_unarray_success( + btype in any::(), + agg in any::(), + semantics in any::(), + arraylen in i32::MIN..i32::MAX + ) { + let mut result = TypeDesc::new(btype, agg, semantics, arraylen); + result.unarray(); + + prop_assert_eq!(result.arraylen, 0); + } + + #[test] + fn test_typedesc_lt_success( + btype_a in any::(), + agg_a in any::(), + semantics_a in any::(), + arraylen_a in i32::MIN..i32::MAX, + btype_b in any::(), + agg_b in any::(), + semantics_b in any::(), + arraylen_b in i32::MIN..i32::MAX, + ) { + let typedesc_a = TypeDesc::new(btype_a, agg_a, semantics_a, arraylen_a); + let typedesc_b = TypeDesc::new(btype_b, agg_b, semantics_b, arraylen_b); + + if btype_a != btype_b { + prop_assert_eq!(typedesc_a < typedesc_b, btype_a < btype_b); + } else if agg_a != agg_b { + prop_assert_eq!(typedesc_a < typedesc_b, agg_a < agg_b); + } else if arraylen_a != arraylen_b { + prop_assert_eq!(typedesc_a < typedesc_b, arraylen_a < arraylen_b); + } else if semantics_a != semantics_b { + prop_assert_eq!(typedesc_a < typedesc_b, semantics_a < semantics_b); + } else { + prop_assert_eq!(typedesc_a < typedesc_b, false); + } + } + + #[test] + fn test_typedesc_basetype_merge_success( + mut btype_a in any::(), + agg_a in any::(), + semantics_a in any::(), + arraylen_a in i32::MIN..i32::MAX, + mut btype_b in any::(), + agg_b in any::(), + semantics_b in any::(), + arraylen_b in i32::MIN..i32::MAX, + ) { + let typedesc_a = TypeDesc::new(btype_a, agg_a, semantics_a, arraylen_a); + let typedesc_b = TypeDesc::new(btype_b, agg_b, semantics_b, arraylen_b); + let result = TypeDesc::basetype_merge(typedesc_a, typedesc_b); + + if btype_a == btype_b { + prop_assert_eq!(result, btype_a); + prop_assert_eq!(result, btype_b); + } else if btype_a == BaseType::Unknown { + prop_assert_eq!(result, btype_b); + } else if btype_b == BaseType::Unknown { + prop_assert_eq!(result, btype_a); + } else { + let size_a = TypeDesc::new(btype_a, Aggregate::Scalar, VecSemantics::NoSemantics, 0).size(); + let size_b = TypeDesc::new(btype_b, Aggregate::Scalar, VecSemantics::NoSemantics, 0).size(); + + if size_a < size_b { + std::mem::swap(&mut btype_a, &mut btype_b); + } + + if btype_a == BaseType::Double || btype_a == BaseType::Float { + prop_assert_eq!(result, btype_a); + } else if btype_a == BaseType::UInt32 && (btype_b == BaseType::UInt16 || btype_b == BaseType::UInt8) { + prop_assert_eq!(result, btype_a); + } else if btype_a == BaseType::Int32 && (btype_b == BaseType::Int16 || btype_b == BaseType::Int8 || btype_b == BaseType::UInt16 || btype_b == BaseType::UInt8) { + prop_assert_eq!(result, btype_a); + } else if (btype_a == BaseType::UInt16 || btype_a == BaseType::Half) && btype_b == BaseType::UInt8 { + prop_assert_eq!(result, btype_a); + } else if (btype_a == BaseType::Int16 || btype_a == BaseType::Half) && (btype_b == BaseType::Int8 || btype_b == BaseType::UInt8) { + prop_assert_eq!(result, btype_a); + } else { + prop_assert_eq!(result, BaseType::Float); + } + } + } + + #[test] + fn test_typedesc_basetype_merge_3_success( + btype_a in any::(), + agg_a in any::(), + semantics_a in any::(), + arraylen_a in i32::MIN..i32::MAX, + btype_b in any::(), + agg_b in any::(), + semantics_b in any::(), + arraylen_b in i32::MIN..i32::MAX, + mut btype_c in any::(), + agg_c in any::(), + semantics_c in any::(), + arraylen_c in i32::MIN..i32::MAX, + ) { + let typedesc_a = TypeDesc::new(btype_a, agg_a, semantics_a, arraylen_a); + let typedesc_b = TypeDesc::new(btype_b, agg_b, semantics_b, arraylen_b); + let typedesc_c = TypeDesc::new(btype_c, agg_c, semantics_c, arraylen_c); + let result = TypeDesc::basetype_merge_3(typedesc_a, typedesc_b, typedesc_c); + let mut btype_inner = TypeDesc::basetype_merge(typedesc_a, typedesc_b); + + if btype_inner == btype_c { + prop_assert_eq!(result, btype_inner); + prop_assert_eq!(result, btype_c); + } else if btype_inner == BaseType::Unknown { + prop_assert_eq!(result, btype_c); + } else if btype_c == BaseType::Unknown { + prop_assert_eq!(result, btype_inner); + } else { + let size_a = TypeDesc::new(btype_inner, Aggregate::Scalar, VecSemantics::NoSemantics, 0).size(); + let size_b = TypeDesc::new(btype_c, Aggregate::Scalar, VecSemantics::NoSemantics, 0).size(); + + if size_a < size_b { + std::mem::swap(&mut btype_inner, &mut btype_c); + } + + if btype_inner == BaseType::Double || btype_inner == BaseType::Float { + prop_assert_eq!(result, btype_inner); + } else if btype_inner == BaseType::UInt32 && (btype_c == BaseType::UInt16 || btype_c == BaseType::UInt8) { + prop_assert_eq!(result, btype_inner); + } else if btype_inner == BaseType::Int32 && (btype_c == BaseType::Int16 || btype_c == BaseType::Int8 || btype_c == BaseType::UInt16 || btype_c == BaseType::UInt8) { + prop_assert_eq!(result, btype_inner); + } else if (btype_inner == BaseType::UInt16 || btype_inner == BaseType::Half) && btype_c == BaseType::UInt8 { + prop_assert_eq!(result, btype_inner); + } else if (btype_inner == BaseType::Int16 || btype_inner == BaseType::Half) && (btype_c == BaseType::Int8 || btype_c == BaseType::UInt8) { + prop_assert_eq!(result, btype_inner); + } else { + prop_assert_eq!(result, BaseType::Float); + } + } + } + + #[test] + fn test_typedesc_convert_type_success( + value_in in i32::MIN..i32::MAX, + ) { + let mut value_out = 0.0f32.to_ne_bytes(); + let src_type = TypeDesc::new(BaseType::Int32, Aggregate::Scalar, VecSemantics::NoSemantics, 0); + let dst_type = TypeDesc::new(BaseType::Float, Aggregate::Scalar, VecSemantics::NoSemantics, 0); + + prop_assert!(TypeDesc::convert_type(src_type, &value_in.to_ne_bytes(), dst_type, &mut value_out, 1)); + prop_assert_eq!(f32::from_ne_bytes(value_out), value_in as f32); + } + } +} From 811842754779ef1a0cd11d582516ca498f26fd01 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Mon, 19 May 2025 14:16:06 -0700 Subject: [PATCH 3/5] CI updates: - Remove some test code - Disable Rust building on `ICC` and `ICX` Signed-off-by: Scott Wilson --- .github/workflows/ci.yml | 2 -- src/build-scripts/ci-rust-test.bash | 6 ------ 2 files changed, 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0b77ec957b..5ecc5d79d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -309,7 +309,6 @@ jobs: OIIO_EXTRA_CPP_ARGS="-fp-model=precise" FREETYPE_VERSION=VER-2-13-0 DISABLE_libuhdr=1 - rust: 1 # For icc, use fp-model precise to eliminate needless LSB errors # that make test results differ from other platforms. - desc: VFX2023 icx/C++17 py3.10 exr3.1 ocio2.2 qt5.15 @@ -326,7 +325,6 @@ jobs: xOPENCOLORIO_CXX=g++ UHDR_CMAKE_C_COMPILER=gcc UHDR_CMAKE_CXX_COMPILER=g++ - rust: 1 # Building libuhdr with icx results in test failures # so we force using gcc/g++. - desc: VFX2024 gcc11/C++17 py3.11 exr3.2 ocio2.3 diff --git a/src/build-scripts/ci-rust-test.bash b/src/build-scripts/ci-rust-test.bash index 715dc576e9..982dff19fd 100755 --- a/src/build-scripts/ci-rust-test.bash +++ b/src/build-scripts/ci-rust-test.bash @@ -14,12 +14,6 @@ fi export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$OpenImageIO_ROOT/lib export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$OpenImageIO_ROOT/lib/pkgconfig -# TEST -echo TEST -cat $OpenImageIO_ROOT/lib/pkgconfig/OpenImageIO.pc -echo END TEST -# END TEST - echo "Using C++ STD ${OIIO_CXX_STANDARD}" echo "Running Rust oiio-sys tests" From b31078ee563aab5ac9b8c483481bd0f531ecfc29 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Mon, 19 May 2025 20:53:06 -0700 Subject: [PATCH 4/5] Add support for building Rust crate by specifying the OIIO include/lib dirs via environment variables Signed-off-by: Scott Wilson --- .github/workflows/ci.yml | 1 - src/build-scripts/ci-rust-test.bash | 2 ++ src/rust/oiio-sys/Cargo.toml | 1 + src/rust/oiio-sys/build.rs | 36 +++++++++++++++++++++++++---- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ecc5d79d1..d35a67f202 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -576,7 +576,6 @@ jobs: cxx_compiler: clang++ cxx_std: 20 python_ver: "3.12" - rust: 1 - desc: MacOS-15-ARM aclang16/C++20/py3.13 runner: macos-15 nametag: macos15-arm-py313 diff --git a/src/build-scripts/ci-rust-test.bash b/src/build-scripts/ci-rust-test.bash index 982dff19fd..6faf62a482 100755 --- a/src/build-scripts/ci-rust-test.bash +++ b/src/build-scripts/ci-rust-test.bash @@ -13,6 +13,8 @@ fi export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$OpenImageIO_ROOT/lib export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$OpenImageIO_ROOT/lib/pkgconfig +export OIIO_INCLUDE_DIR=$OpenImageIO_ROOT/include +export OIIO_LIBRARY_DIR=$OpenImageIO_ROOT/lib echo "Using C++ STD ${OIIO_CXX_STANDARD}" diff --git a/src/rust/oiio-sys/Cargo.toml b/src/rust/oiio-sys/Cargo.toml index 1e055add53..fec6936b0d 100644 --- a/src/rust/oiio-sys/Cargo.toml +++ b/src/rust/oiio-sys/Cargo.toml @@ -22,6 +22,7 @@ cxx = { version = "1.0.158", features = ["c++17"] } [build-dependencies] anyhow = "1.0.98" cxx-build = "1.0.158" +glob = "0.3.2" pkg-config = "0.3.32" [dev-dependencies] diff --git a/src/rust/oiio-sys/build.rs b/src/rust/oiio-sys/build.rs index 853fc3afca..d03c33528b 100644 --- a/src/rust/oiio-sys/build.rs +++ b/src/rust/oiio-sys/build.rs @@ -15,9 +15,37 @@ fn main() -> Result<()> { } let mut include_paths = Vec::new(); + let mut lib_paths = Vec::new(); + let mut libs = Vec::new(); - let pkgconfig = pkg_config::probe_library("OpenImageIO")?; - include_paths.extend(pkgconfig.include_paths); + let oiio_include_dir = std::env::var("OIIO_INCLUDE_DIR").map(|v| std::path::PathBuf::from(v)); + let oiio_lib_dir = std::env::var("OIIO_LIBRARY_DIR").map(|v| std::path::PathBuf::from(v)); + + if oiio_include_dir.is_ok() && oiio_lib_dir.is_ok() { + let oiio_include_dir = oiio_include_dir.unwrap(); + let oiio_lib_dir = oiio_lib_dir.unwrap(); + + if glob::glob( + oiio_lib_dir + .join("libOpenImageIO_d.*") + .to_string_lossy() + .as_ref(), + ) + .is_ok() + { + libs.extend_from_slice(&["OpenImageIO_d".into(), "OpenImageIO_Util_d".into()]); + } else { + libs.extend_from_slice(&["OpenImageIO".into(), "OpenImageIO_Util".into()]); + } + + include_paths.push(oiio_include_dir); + lib_paths.push(oiio_lib_dir); + } else { + let pkgconfig = pkg_config::probe_library("OpenImageIO")?; + include_paths.extend(pkgconfig.include_paths); + lib_paths.extend(pkgconfig.link_paths); + libs.extend(pkgconfig.libs); + } let std_version = std::env::var("OIIO_CXX_STANDARD").unwrap_or("c++17".to_string()); @@ -36,11 +64,11 @@ fn main() -> Result<()> { println!("cargo:rerun-if-changed=include/{}.h", name); } - for link_path in pkgconfig.link_paths { + for link_path in lib_paths { println!("cargo:rustc-link-search=native={}", link_path.display()); } - for lib in pkgconfig.libs { + for lib in libs { println!("cargo:rustc-link-lib={}", lib); } From c20170b852ff87a8d3c2144f548b3bae1b681efd Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Mon, 19 May 2025 21:11:19 -0700 Subject: [PATCH 5/5] Small build fix - Should try to see if we can get a path from the lib dir Signed-off-by: Scott Wilson --- src/rust/oiio-sys/build.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/rust/oiio-sys/build.rs b/src/rust/oiio-sys/build.rs index d03c33528b..9353471d1c 100644 --- a/src/rust/oiio-sys/build.rs +++ b/src/rust/oiio-sys/build.rs @@ -24,15 +24,15 @@ fn main() -> Result<()> { if oiio_include_dir.is_ok() && oiio_lib_dir.is_ok() { let oiio_include_dir = oiio_include_dir.unwrap(); let oiio_lib_dir = oiio_lib_dir.unwrap(); - - if glob::glob( + let mut paths = glob::glob( oiio_lib_dir .join("libOpenImageIO_d.*") .to_string_lossy() .as_ref(), ) - .is_ok() - { + .expect("Could not open the OpenImageIO directory."); + + if paths.next().is_some() { libs.extend_from_slice(&["OpenImageIO_d".into(), "OpenImageIO_Util_d".into()]); } else { libs.extend_from_slice(&["OpenImageIO".into(), "OpenImageIO_Util".into()]);