diff --git a/.github/workflows/conformance.yml b/.github/workflows/conformance.yml index 4eeb4ecf..3146264f 100644 --- a/.github/workflows/conformance.yml +++ b/.github/workflows/conformance.yml @@ -53,10 +53,49 @@ jobs: uses: actions/upload-artifact@v4 with: name: build-artifacts + # `include/` is bundled so the downstream benchmark job can build + # openvx-mark against rustVX without needing to check out the + # rustVX source tree. path: | target/release/libopenvx_ffi.so OpenVX-cts/build/bin/vx_test_conformance OpenVX-cts/test_data/ + include/ + retention-days: 1 + + # Build the Khronos OpenVX sample implementation in its own phase, in + # parallel with the rustVX `build` job, and upload the resulting library + # + headers as a self-contained archive. The benchmark job below pulls + # both archives down onto a single runner so rustVX and the Khronos + # sample are exercised on identical hardware. + build-khronos-sample: + name: Build Khronos OpenVX sample + runs-on: ubuntu-22.04 + steps: + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential cmake git python3 + - name: Build Khronos OpenVX sample + run: | + git clone --recursive --depth 1 \ + https://github.com/KhronosGroup/OpenVX-sample-impl.git khronos-sample + cd khronos-sample + python3 Build.py --os=Linux --arch=64 --conf=Release + - name: Stage Khronos sample archive + run: | + set -euo pipefail + LIB_SRC=$(dirname $(find khronos-sample -name "libopenvx.so" -not -path "*/build/*" | head -1)) + echo "Khronos libraries discovered in: $LIB_SRC" + mkdir -p khronos-stage/lib + cp "$LIB_SRC"/libopenvx*.so "$LIB_SRC"/libvxu*.so khronos-stage/lib/ + cp -r khronos-sample/api-docs/include khronos-stage/include + ls -R khronos-stage + - name: Upload Khronos sample artifacts + uses: actions/upload-artifact@v4 + with: + name: khronos-sample-artifacts + path: khronos-stage/ retention-days: 1 baseline: @@ -277,3 +316,177 @@ jobs: export LD_LIBRARY_PATH=${{ github.workspace }}/target/release export VX_TEST_DATA_PATH=${{ github.workspace }}/OpenVX-cts/test_data/ timeout 300 ./bin/vx_test_conformance --filter="GaussianPyramid.*:LaplacianPyramid.*:LaplacianReconstruct.*:OptFlowPyrLK.*" + + # Performance benchmark using openvx-mark, comparing rustVX against the + # Khronos OpenVX sample implementation on the SAME runner so the two + # numbers come from identical hardware. This job does NOT rebuild either + # implementation — it just downloads the archives produced by the + # `build` and `build-khronos-sample` phases above, builds the openvx-mark + # tool against each, runs the same workload, and compares the JSON + # reports. The CTS jobs above use `continue-on-error: true`, so this + # job effectively gates on `build`, `build-khronos-sample`, and + # `baseline` succeeding (matching the existing CTS gate). + benchmark: + name: Benchmark & compare (rustVX vs Khronos sample) + runs-on: ubuntu-22.04 + needs: + - build + - build-khronos-sample + - baseline + - graph + - data-objects + - image-ops + - vision-color + - vision-filters + - vision-arithmetic + - vision-geometric + - vision-features + - vision-statistics + - vision-pyramid + continue-on-error: true + steps: + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y build-essential cmake git python3 + + - name: Download rustVX archive + uses: actions/download-artifact@v4 + with: + name: build-artifacts + path: ${{ github.workspace }}/rustvx-pkg + + - name: Download Khronos sample archive + uses: actions/download-artifact@v4 + with: + name: khronos-sample-artifacts + path: ${{ github.workspace }}/khronos-pkg + + - name: Expose rustVX as libopenvx / libvxu + id: rustvx + # openvx-mark uses `find_library(NAMES openvx)` and + # `find_library(NAMES vxu)`. rustVX ships a single + # `libopenvx_ffi.so` that exports the full set of `vx*`/`vxu*` + # symbols, so symlink the two classic Khronos library names to + # it without changing rustVX's own build output. + run: | + set -euo pipefail + LIB_DIR=${{ github.workspace }}/rustvx-pkg/target/release + chmod -R u+rwX "$LIB_DIR" + cd "$LIB_DIR" + ln -sf libopenvx_ffi.so libopenvx.so + ln -sf libopenvx_ffi.so libvxu.so + ls -la libopenvx*.so libvxu*.so + echo "lib_dir=$LIB_DIR" >> "$GITHUB_OUTPUT" + echo "include_dir=${{ github.workspace }}/rustvx-pkg/include" >> "$GITHUB_OUTPUT" + + - name: Inspect Khronos sample archive + id: khronos + run: | + set -euo pipefail + LIB_DIR=${{ github.workspace }}/khronos-pkg/lib + INCLUDE_DIR=${{ github.workspace }}/khronos-pkg/include + ls -la "$LIB_DIR" + echo "lib_dir=$LIB_DIR" >> "$GITHUB_OUTPUT" + echo "include_dir=$INCLUDE_DIR" >> "$GITHUB_OUTPUT" + + - name: Clone openvx-mark + run: | + git clone --depth 1 https://github.com/kiritigowda/openvx-mark.git \ + ${{ github.workspace }}/openvx-mark + + # --------------------------------------------------------------------- + # rustVX benchmark + # --------------------------------------------------------------------- + - name: Build openvx-mark against rustVX + run: | + mkdir -p ${{ github.workspace }}/openvx-mark/build-rustvx + cd ${{ github.workspace }}/openvx-mark/build-rustvx + cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DOPENVX_INCLUDES=${{ steps.rustvx.outputs.include_dir }} \ + -DOPENVX_LIB_DIR=${{ steps.rustvx.outputs.lib_dir }} \ + .. + cmake --build . -j$(nproc) + + - name: Run benchmark (rustVX) + run: | + cd ${{ github.workspace }}/openvx-mark/build-rustvx + export LD_LIBRARY_PATH=${{ steps.rustvx.outputs.lib_dir }}:$LD_LIBRARY_PATH + ./openvx-mark --resolution VGA --iterations 10 --warmup 3 + + # --------------------------------------------------------------------- + # Khronos sample benchmark + # --------------------------------------------------------------------- + - name: Build openvx-mark against Khronos sample + run: | + mkdir -p ${{ github.workspace }}/openvx-mark/build-khronos + cd ${{ github.workspace }}/openvx-mark/build-khronos + cmake \ + -DCMAKE_BUILD_TYPE=Release \ + -DOPENVX_INCLUDES=${{ steps.khronos.outputs.include_dir }} \ + -DOPENVX_LIB_DIR=${{ steps.khronos.outputs.lib_dir }} \ + .. + cmake --build . -j$(nproc) + + - name: Run benchmark (Khronos sample) + run: | + cd ${{ github.workspace }}/openvx-mark/build-khronos + export LD_LIBRARY_PATH=${{ steps.khronos.outputs.lib_dir }}:$LD_LIBRARY_PATH + ./openvx-mark --resolution VGA --iterations 10 --warmup 3 + + # --------------------------------------------------------------------- + # Compare results + # --------------------------------------------------------------------- + - name: Compare benchmark results (rustVX vs Khronos) + run: | + RUSTVX=${{ github.workspace }}/openvx-mark/build-rustvx/benchmark_results/benchmark_results.json + KHRONOS=${{ github.workspace }}/openvx-mark/build-khronos/benchmark_results/benchmark_results.json + + if [ ! -f "$RUSTVX" ] || [ ! -f "$KHRONOS" ]; then + echo "Skipping comparison — one or both benchmark results missing" + ls -la "$(dirname $RUSTVX)" 2>/dev/null || true + ls -la "$(dirname $KHRONOS)" 2>/dev/null || true + exit 0 + fi + + # First report is the "candidate" (rustVX); second is the "baseline" + # (Khronos). The Speedup column shows how much faster the candidate + # is than the baseline (>1.0x = rustVX wins). + python3 ${{ github.workspace }}/openvx-mark/scripts/compare_reports.py \ + "$RUSTVX" "$KHRONOS" \ + --output ${{ github.workspace }}/openvx-mark/comparison + + - name: Post comparison to job summary + if: always() + run: | + COMPARISON=${{ github.workspace }}/openvx-mark/comparison.md + if [ -f "$COMPARISON" ]; then + cat "$COMPARISON" >> "$GITHUB_STEP_SUMMARY" + else + echo "_No comparison report was produced._" >> "$GITHUB_STEP_SUMMARY" + fi + + - name: Upload rustVX benchmark results + if: always() + uses: actions/upload-artifact@v4 + with: + name: benchmark-results-rustvx + path: ${{ github.workspace }}/openvx-mark/build-rustvx/benchmark_results/ + if-no-files-found: ignore + + - name: Upload Khronos sample benchmark results + if: always() + uses: actions/upload-artifact@v4 + with: + name: benchmark-results-khronos-sample + path: ${{ github.workspace }}/openvx-mark/build-khronos/benchmark_results/ + if-no-files-found: ignore + + - name: Upload comparison report + if: always() + uses: actions/upload-artifact@v4 + with: + name: benchmark-comparison + path: ${{ github.workspace }}/openvx-mark/comparison.* + if-no-files-found: ignore diff --git a/LICENSE b/LICENSE index 9d78b711..f44d0645 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Kiriti Nagesh Gowda +Copyright (c) 2026 Kiriti Nagesh Gowda Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 5fbb97bb..7ab25382 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,29 @@ +

+ + OpenVX + +

+ # rustVX [![OpenVX Conformance](https://github.com/kiritigowda/rustVX/actions/workflows/conformance.yml/badge.svg?branch=develop)](https://github.com/kiritigowda/rustVX/actions/workflows/conformance.yml) +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![Rust](https://img.shields.io/badge/rust-stable-orange.svg)](https://www.rust-lang.org/) An [OpenVX 1.3.1](https://www.khronos.org/openvx/) implementation written in Rust. rustVX provides the complete OpenVX Vision Feature Set through a standard C API (`libopenvx_ffi`), enabling existing OpenVX applications to use a memory-safe, portable backend with no source changes. +## Conformance Status + +rustVX passes the full [Khronos OpenVX 1.3 Conformance Test Suite](https://github.com/KhronosGroup/OpenVX-cts) for both required profiles: + +| Profile | Required tests | Passing | +|---------|----------------|---------| +| OpenVX baseline | 863 | **863 / 863** | +| Vision conformance profile | 4957 | **4957 / 4957** | +| **Total enabled** | **5820** | **5820 / 5820** | + +Latest CTS run results are published on each push and pull request via the [Actions tab](https://github.com/kiritigowda/rustVX/actions). + ## Project Structure ``` @@ -49,7 +69,10 @@ The shared library is produced at: ## Running Conformance Tests -The [OpenVX Conformance Test Suite](https://github.com/simonCatBot/OpenVX-cts) is included as a git submodule. Build and run it against the rustVX library: +The [Khronos OpenVX Conformance Test Suite](https://github.com/KhronosGroup/OpenVX-cts) is included as a git submodule. Build and run it against the rustVX library: + +> [!NOTE] +> The `-DCMAKE_POLICY_VERSION_MINIMUM=3.5` flag below is needed when configuring with CMake 4.0+, which dropped compatibility with the older `cmake_minimum_required` versions used by the upstream CTS. It is harmless on older CMake. ### Linux @@ -59,6 +82,7 @@ cd OpenVX-cts mkdir -p build && cd build cmake .. \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_C_STANDARD_LIBRARIES="-lm" \ -DCMAKE_CXX_STANDARD_LIBRARIES="-lm" \ -DOPENVX_INCLUDES="$(pwd)/../../include;$(pwd)/../include" \ @@ -80,6 +104,7 @@ cd OpenVX-cts mkdir -p build && cd build cmake .. \ -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DOPENVX_INCLUDES="$(pwd)/../../include;$(pwd)/../include" \ -DOPENVX_LIBRARIES="$(pwd)/../../target/release/libopenvx_ffi.dylib" \ -DOPENVX_CONFORMANCE_VISION=ON @@ -99,6 +124,7 @@ cd OpenVX-cts mkdir build; cd build cmake .. ` -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_POLICY_VERSION_MINIMUM=3.5 ` -DOPENVX_INCLUDES="$PWD\..\..\include;$PWD\..\include" ` -DOPENVX_LIBRARIES="$PWD\..\..\target\release\openvx_ffi.dll.lib" ` -DOPENVX_CONFORMANCE_VISION=ON @@ -124,22 +150,26 @@ Use the `--filter` flag to run a subset of tests: ## Continuous Integration -GitHub Actions builds and runs the full CTS on every push and pull request. The workflow splits tests into parallel jobs for faster feedback: +GitHub Actions builds and runs the full CTS on every push and pull request. The workflow splits the suite into parallel jobs for faster feedback: -- **baseline** -- Graph, Logging, SmokeTest, Target -- **graph** -- Graph callbacks, delays, ROI, UserNode -- **data-objects** -- Scalar, Array, Matrix, Convolution, Distribution, LUT, Histogram -- **image-ops** -- Image, CopyImagePatch, MapImagePatch, Remap -- **vision-color** -- ColorConvert, ChannelExtract, ChannelCombine, ConvertDepth -- **vision-filters** -- Box, Gaussian, Median, Dilate, Erode, Sobel, Convolve, EqualizeHistogram, NonLinearFilter -- **vision-arithmetic** -- Add, Subtract, Multiply, Bitwise, Not, WeightedAverage, Threshold -- **vision-geometric** -- Scale, WarpAffine, WarpPerspective, Remap, HalfScaleGaussian -- **vision-features** -- HarrisCorners, FastCorners, Canny -- **vision-statistics** -- MeanStdDev, MinMaxLoc, Integral -- **vision-pyramid** -- GaussianPyramid, LaplacianPyramid, LaplacianReconstruct, OptFlowPyrLK +| Job | Test categories | Pipeline status | +|-----|-----------------|-----------------| +| **baseline** | GraphBase, Logging, SmokeTest, Target | [![baseline](https://img.shields.io/github/check-runs/kiritigowda/rustVX/develop?nameFilter=baseline&label=)](https://github.com/kiritigowda/rustVX/actions/workflows/conformance.yml?query=branch%3Adevelop) | +| **graph** | Graph framework (cycles, virtual data, multi-run, replicate node), GraphCallback, GraphDelay, GraphROI, UserNode | [![graph](https://img.shields.io/github/check-runs/kiritigowda/rustVX/develop?nameFilter=graph&label=)](https://github.com/kiritigowda/rustVX/actions/workflows/conformance.yml?query=branch%3Adevelop) | +| **data-objects** | Scalar, Array, ObjectArray, Matrix, Convolution, Distribution, LUT, Histogram | [![data-objects](https://img.shields.io/github/check-runs/kiritigowda/rustVX/develop?nameFilter=data-objects&label=)](https://github.com/kiritigowda/rustVX/actions/workflows/conformance.yml?query=branch%3Adevelop) | +| **image-ops** | Image, CopyImagePatch, MapImagePatch, CreateImageFromChannel, Remap | [![image-ops](https://img.shields.io/github/check-runs/kiritigowda/rustVX/develop?nameFilter=image-ops&label=)](https://github.com/kiritigowda/rustVX/actions/workflows/conformance.yml?query=branch%3Adevelop) | +| **vision-color** | ColorConvert, ChannelExtract, ChannelCombine, ConvertDepth | [![vision-color](https://img.shields.io/github/check-runs/kiritigowda/rustVX/develop?nameFilter=vision-color&label=)](https://github.com/kiritigowda/rustVX/actions/workflows/conformance.yml?query=branch%3Adevelop) | +| **vision-filters** | Box, Gaussian, Median, Dilate, Erode, Sobel, Magnitude, Phase, NonLinearFilter, Convolve, EqualizeHistogram | [![vision-filters](https://img.shields.io/github/check-runs/kiritigowda/rustVX/develop?nameFilter=vision-filters&label=)](https://github.com/kiritigowda/rustVX/actions/workflows/conformance.yml?query=branch%3Adevelop) | +| **vision-arithmetic** | Add, Subtract, Multiply, Bitwise (And/Or/Xor/Not), WeightedAverage, Threshold | [![vision-arithmetic](https://img.shields.io/github/check-runs/kiritigowda/rustVX/develop?nameFilter=vision-arithmetic&label=)](https://github.com/kiritigowda/rustVX/actions/workflows/conformance.yml?query=branch%3Adevelop) | +| **vision-geometric** | Scale, WarpAffine, WarpPerspective, Remap, HalfScaleGaussian | [![vision-geometric](https://img.shields.io/github/check-runs/kiritigowda/rustVX/develop?nameFilter=vision-geometric&label=)](https://github.com/kiritigowda/rustVX/actions/workflows/conformance.yml?query=branch%3Adevelop) | +| **vision-features** | HarrisCorners, FastCorners, Canny | [![vision-features](https://img.shields.io/github/check-runs/kiritigowda/rustVX/develop?nameFilter=vision-features&label=)](https://github.com/kiritigowda/rustVX/actions/workflows/conformance.yml?query=branch%3Adevelop) | +| **vision-statistics** | MeanStdDev, MinMaxLoc, Integral | [![vision-statistics](https://img.shields.io/github/check-runs/kiritigowda/rustVX/develop?nameFilter=vision-statistics&label=)](https://github.com/kiritigowda/rustVX/actions/workflows/conformance.yml?query=branch%3Adevelop) | +| **vision-pyramid** | GaussianPyramid, LaplacianPyramid, LaplacianReconstruct, OptFlowPyrLK | [![vision-pyramid](https://img.shields.io/github/check-runs/kiritigowda/rustVX/develop?nameFilter=vision-pyramid&label=)](https://github.com/kiritigowda/rustVX/actions/workflows/conformance.yml?query=branch%3Adevelop) | -See the [Actions tab](https://github.com/kiritigowda/rustVX/actions) for latest results. +See the [Actions tab](https://github.com/kiritigowda/rustVX/actions) for full run history. ## License -MIT +This project is licensed under the [MIT License](LICENSE). + +The OpenVX logo is a trademark of [The Khronos Group Inc.](https://www.khronos.org/legal/trademarks) The vector logo file in `docs/openvx-logo.svg` is sourced from [Wikimedia Commons](https://commons.wikimedia.org/wiki/File:OpenVX_logo.svg) and is included for identification purposes only. diff --git a/docs/openvx-logo.svg b/docs/openvx-logo.svg new file mode 100644 index 00000000..cd1717f9 --- /dev/null +++ b/docs/openvx-logo.svg @@ -0,0 +1,18 @@ + +OpenVX logoAn open standard for computer vision by Khronos Groupimage/svg+xml + + + + + + + + + + + + + + + + diff --git a/openvx-core/src/unified_c_api.rs b/openvx-core/src/unified_c_api.rs index 155c20e7..9053f65a 100644 --- a/openvx-core/src/unified_c_api.rs +++ b/openvx-core/src/unified_c_api.rs @@ -804,9 +804,15 @@ fn infer_virtual_image_dimensions( kernel_name: &str, ) -> Option<(u32, u32, vx_df_image)> { // First, check if the virtual image already has explicit dimensions + // (we treat `VX_DF_IMAGE_VIRT` as "format unknown", since it just + // means the user did not commit to a format at creation time). if let Ok(registry) = VIRTUAL_IMAGES.lock() { if let Some(info) = registry.get(&(image_id as usize)) { - if info.width > 0 && info.height > 0 && info.format != 0 { + if info.width > 0 + && info.height > 0 + && info.format != 0 + && info.format != VX_DF_IMAGE_VIRT + { return Some((info.width, info.height, info.format as vx_df_image)); } } @@ -820,7 +826,7 @@ fn infer_virtual_image_dimensions( if img.width > 0 && img.height > 0 { let format = if let Ok(registry) = VIRTUAL_IMAGES.lock() { if let Some(info) = registry.get(&(image_id as usize)) { - if info.format != 0 { + if info.format != 0 && info.format != VX_DF_IMAGE_VIRT { info.format } else { img.format @@ -831,12 +837,36 @@ fn infer_virtual_image_dimensions( } else { img.format }; - if format != 0 { + if format != 0 && format != VX_DF_IMAGE_VIRT { return Some((img.width, img.height, format as vx_df_image)); } } } + // Read any user-specified format on this virtual image. If the user + // created the image as `vxCreateVirtualImage(g, 0, 0, FORMAT)`, we + // must respect FORMAT and only infer the dimensions from the + // producer node — otherwise we silently retype the image (e.g. + // forcing a user-specified U8 to S16 because Magnitude produces + // S16), which then breaks downstream nodes such as ConvertDepth and + // Threshold with `VX_ERROR_INVALID_FORMAT`. + let user_format: Option = { + let img_fmt = unsafe { (&*(image_id as *const VxCImage)).format }; + if img_fmt != 0 && img_fmt != VX_DF_IMAGE_VIRT { + Some(img_fmt) + } else if let Ok(registry) = VIRTUAL_IMAGES.lock() { + registry.get(&(image_id as usize)).and_then(|info| { + if info.format != 0 && info.format != VX_DF_IMAGE_VIRT { + Some(info.format as vx_df_image) + } else { + None + } + }) + } else { + None + } + }; + // Find which node produces this image (it's an output of that node) let producer_node = if let Some(producer) = param_to_producer.get(&image_id) { *producer @@ -890,7 +920,8 @@ fn infer_virtual_image_dimensions( } if input_width > 0 && input_height > 0 { - let format = infer_output_format(kernel_name, &input_formats); + let format = + user_format.unwrap_or_else(|| infer_output_format(kernel_name, &input_formats)); return Some((input_width, input_height, format)); } @@ -1491,23 +1522,51 @@ pub extern "C" fn vxVerifyGraph(graph: vx_graph) -> vx_status { } } - // Allocate backing storage for virtual images + // Allocate backing storage for virtual images. + // + // A virtual image may reach this point in three states: + // (a) Fully resolved at creation time + // (`vxCreateVirtualImage(g, w, h, FORMAT)`): width, height + // and format are all set, but the data buffer was created + // empty by `vxCreateVirtualImage` and still needs to be + // sized for the format. + // (b) Format known, dimensions unknown + // (`vxCreateVirtualImage(g, 0, 0, FORMAT)`): the user + // explicitly chose a format; we must preserve it and only + // infer dimensions from the producer. + // (c) Both format and dimensions unknown (`VX_DF_IMAGE_VIRT`): + // infer everything from the producer node. for (node_id, params) in &node_params { for param_opt in params.iter() { if let Some(param_ref) = param_opt { if is_image_reference(*param_ref) && is_virtual_image(*param_ref) { - // Skip if already allocated (dimensions and format set) - unsafe { + // Snapshot the current state of the virtual image. + let (cur_w, cur_h, cur_fmt, data_len) = unsafe { let img = &*(*param_ref as *const VxCImage); - if img.width > 0 - && img.height > 0 - && img.format != 0 - && img.format != VX_DF_IMAGE_VIRT - { - continue; // already allocated + let dl = img.data.read().map(|d| d.len()).unwrap_or(0); + (img.width, img.height, img.format, dl) + }; + let dims_known = cur_w > 0 + && cur_h > 0 + && cur_fmt != 0 + && cur_fmt != VX_DF_IMAGE_VIRT; + + if dims_known { + // Case (a): only the data buffer may be missing. + let expected = VxCImage::calculate_size(cur_w, cur_h, cur_fmt); + if data_len < expected { + if allocate_virtual_image_storage( + *param_ref, cur_w, cur_h, cur_fmt, + ) + .is_err() + { + return VX_ERROR_NO_MEMORY; + } } + continue; } - // Virtual image - determine dimensions from connected nodes + + // Cases (b) and (c): infer what's missing. let kernel_name = node_kernel_names .get(node_id) .map(|s| s.as_str()) @@ -8128,6 +8187,49 @@ pub extern "C" fn vxUnmapTensorPatch(tensor: vx_tensor, _map_id: usize) -> i32 { 0 } +/// `vxCopyTensorPatch` — copy a patch of tensor data to/from user memory. +/// +/// rustVX does not (yet) implement the OpenVX tensor object beyond the +/// minimal stubs required by the conformance test suite. This entry point +/// exists so that downstream consumers (for example `openvx-mark`) can +/// link against `libopenvx_ffi.so` without missing symbols. Calls return +/// `VX_ERROR_NOT_IMPLEMENTED`, allowing tensor-using benchmarks to report +/// graceful unsupported status rather than aborting at link time. +#[no_mangle] +pub extern "C" fn vxCopyTensorPatch( + tensor: vx_tensor, + _number_of_dims: vx_size, + _view_start: *const vx_size, + _view_end: *const vx_size, + _user_stride: *const vx_size, + _user_ptr: *mut c_void, + _usage: vx_enum, + _user_memory_type: vx_enum, +) -> vx_status { + if tensor.is_null() { + return VX_ERROR_INVALID_REFERENCE; + } + VX_ERROR_NOT_IMPLEMENTED +} + +/// `vxCopyNode` — create a node that copies one OpenVX object to another. +/// +/// The OpenVX 1.3 spec provides this as a generic data-copy node usable in +/// graph mode. rustVX does not implement it yet, but we expose a stub so +/// downstream tools that reference the symbol (e.g. `openvx-mark`) can +/// still link. The stub returns `NULL` and sets the spec-defined error +/// behaviour: callers should check the result with `vxGetStatus` and will +/// observe `VX_ERROR_NOT_IMPLEMENTED`. +#[no_mangle] +pub extern "C" fn vxCopyNode( + graph: vx_graph, + input: vx_reference, + output: vx_reference, +) -> vx_node { + let _ = (graph, input, output); + std::ptr::null_mut() +} + #[no_mangle] pub extern "C" fn vxReleaseTensor(tensor: *mut vx_tensor) -> i32 { if tensor.is_null() {