C API: Generic WPM Contributions#

This C++ example uses the C API to read Contributions, copy named tensors and CPU parameters, and save valid records to CSV. It also demonstrates coordinated selector updates, rejected-edit retention and recovery. The Python example uses the same recipes and scene.

Build and Run#

Use a C++17 compiler, CMake 3.16 or newer, an RTX GPU and a supported driver. Both ovrtx and ovstage SDKs must contain the APIs and schemas used by this checkout and come from the same release train. The neighboring examples’ package pins are not a verified minimum version for Generic WPM. See Getting Started in C for SDK setup and loader requirements.

The program takes the ovrtx public-source root as its first argument. Keep its tests/docs and examples/python/generic-wpm directories available: the consumer reuses their recipe layers, scene and cube mesh. It does not bake in a build-machine asset path.

From examples/c/generic-wpm, configure against containing SDKs and run:

cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="<ovrtx-sdk>;<ovstage-sdk>"
cmake --build build --config Release
./build/generic-wpm ../../.. depth _output/depth.csv
./build/generic-wpm ../../.. gaussian_bloom _output/bloom.csv
./build/generic-wpm ../../.. mixed _output/mixed.csv --live-edits

On Windows, use build/Release/generic-wpm.exe. The example links both shared loaders; CMake prepares their runtime dependencies. The first capture may spend several minutes compiling shaders.

The final three arguments are optional: recipe defaults to depth and output defaults to _output/contributions.csv. --live-edits requires mixed and an explicit output argument. Renderer logs are saved beside the CSV.

Read and Filter the Output#

The consumer steps the attached stage, waits, fetches the results, and finds the output by its full RenderVar path. It maps to CPU, looks up tensors and parameters by name, and copies them before unmapping. The capture workflow shows the paired Python/C stepping and mapping calls, including their lifetime rules.

The snippets below preserve logical coordinates while filtering invalid slots. The C API example writes each valid path to CSV; the Python example gathers the corresponding arrays for NPZ/Rerun output:

main.cpp, from the runnable C example.#
for (size_t slot = 0; slot < captured.valid.size(); ++slot) {
    if (!captured.valid[slot]) {
        continue; // Dense holes are not returns.
    }
    size_t index = slot;
    std::array<size_t, 5> coordinates{};
    for (int axis = 4; axis >= 0; --axis) {
        coordinates[axis] = index % captured.shape[axis];
        index /= captured.shape[axis];
    }
    // Coordinates are tile, transmitter, ray, bounce, receiver slot
    // (fastest).
    csv << ordinal << ',' << slot;
    for (auto coordinate : coordinates) {
        csv << ',' << coordinate;
    }
    csv << ',' << captured.distance[slot];
    for (int axis = 0; axis < 3; ++axis) {
        csv << ',' << captured.hit_world[3 * slot + axis];
    }
    csv << ',';
    // Only the colocated, one-bounce depth recipe has range = round-trip
    // / 2.
    if (depth) {
        csv << captured.distance[slot] / 2;
    }
    csv << '\n';
}
main.py, from the runnable Python example.#
import numpy as np

# These are owned copies from capture(), not borrowed GPU or mapped CPU data.
shape = tuple(int(params[name].item()) for name in ("numTiles", "numTx", "raysPerTx", "maxBounces", "groupSize"))
valid = tensors["Valid"].reshape(shape) != 0
# Keep logical slot coordinates before compacting the valid payloads.
slot_indices = np.argwhere(valid)
hit_samples = tensors["HitPosWorld"].reshape(*shape, 3)[valid]
path_lengths = tensors["RoundTripDistanceM"].reshape(shape)[valid]

Example Results#

One recorded depth run wrote 15 records. Slot 7 has an 11 m range; oblique paths are longer. Only this co-located, one-bounce recipe fills range_m. The other recipes leave that column empty and report total round_trip_m instead.

gaussian_bloom allocates 1,215 slots; one recorded run wrote 819 valid records. Their original slot IDs preserve the gaps caused by clipped receiver neighborhoods.

One recorded mixed --live-edits run wrote five captures with 2, 6, 18, 18 and 2 records. The corresponding allocated capacities are 2, 6, 18, 18 and 2. The fourth request, Tx/Rx/page 0/1/0, is invalid and retains accepted selectors 2/1/1. The last request restores 0/0/0. The console prints requested and accepted selectors, while the CSV identifies captures by ordinal. See Change Active Patterns for the coordinated write/seal/step calls. The program exits nonzero if its scene-specific count, depth-range or retention checks fail. These checks verify this example scene, not a valid-count guarantee for arbitrary scenes.

Positions in this origin-centered, meter-unit scene can be plotted directly. For other coordinate systems, consult the guide’s channel units; do not assume HitPosWorld is absolute world meters. The example does not read DeltaTimeNs.