Renderer Configuration#

The renderer owns ovrtx’s GPU resources, stream-ordered work queue, and rendering pipeline. In standalone mode it also owns a runtime stage that you load USD into. In attached mode (ovrtx 0.4+) it renders scene state managed by an external ovstage instance instead. Create one renderer before loading USD or stepping RenderProducts.

Creating a Renderer#

# Create the Renderer and attach the stage that owns scene data.
print("Creating renderer. The first run of the application will take some time as shaders are compiled and cached...", file=sys.stderr)
renderer = ovrtx.Renderer()
stage = ovstage.Stage("ovrtx.example.minimal")
renderer.attach_ovstage(stage)
print("Renderer created.", file=sys.stderr)

Configuration entries are passed through RendererConfig:

config = ovrtx.RendererConfig(
    sync_mode=True,
    log_file_path=str(output_dir / "config-test.log"),
    log_level="info",
)
renderer = ovrtx.Renderer(config=config)
assert any(component > 0 for component in renderer.version)
assert renderer.config.sync_mode is True
assert renderer.config.log_level == "info"
assert renderer.config.log_file_path == str(output_dir / "config-test.log")
// Create the renderer, providing configuration settings.
//
// The STATIC ovrtx loader resolves the ${executable_dir} token to the running
// executable's directory, so we hand it the token instead of computing the path
// in client code. ovrtx_setup_runtime() links the package bin beside the exe as
// "ovrtx/".
ovx_string_t ovrtx_package_root = {
    OVX_CONFIG_EXECUTABLE_DIR_TOKEN "/ovrtx",
    sizeof(OVX_CONFIG_EXECUTABLE_DIR_TOKEN "/ovrtx") - 1};
ovrtx_config_entry_t config_entries[] = {
    ovrtx_config_entry_binary_package_root_path(ovrtx_package_root),
};
ovrtx_config_t config {};
config.entries = config_entries;
config.entry_count = sizeof(config_entries) / sizeof(config_entries[0]);
std::cerr << "Creating renderer. The first run of the application will take some time as shaders are compiled and cached..." << std::endl;
result = ovrtx_create_renderer(&config, &renderer);
if (check_and_print_error(result, "create_renderer")) {
    return 1;
}
std::cerr << "Renderer created." << std::endl;

Version and config-entry setup use ovrtx_config_t:

uint32_t major = 0;
uint32_t minor = 0;
uint32_t patch = 0;
ovrtx_get_version(&major, &minor, &patch);

ovrtx_config_entry_t entries[] = {
    ovrtx_config_entry_log_level(ovx_str("info")),
    ovrtx_config_entry_sync_mode(true),
};
ovrtx_config_t config{entries, 2};

ovrtx_renderer_t* configured_renderer = nullptr;
ovrtx_result_t create_result = ovrtx_create_renderer(&config, &configured_renderer);
ASSERT_API_SUCCESS(create_result.status);
ASSERT_NE(configured_renderer, nullptr);
ovrtx_destroy_renderer(configured_renderer);

Configuration Entries#

Common configuration entries include:

RendererConfig entry

Description

log_file_path=...

Write renderer logs to a file.

log_level=...

Set log verbosity.

binary_package_root_path=...

Point to a custom binary package root. May include ${executable_dir} (see OVX_CONFIG_EXECUTABLE_DIR_TOKEN) to anchor the path at the running executable’s directory.

keep_system_alive=True

Keep shared graphics resources alive after the last renderer.

active_cuda_gpus="0,1"

Restrict renderer-level CUDA-visible devices.

use_vulkan=True

Select the Vulkan backend where supported.

motion_bvh=...

Motion BVH mode: "disable" (default), "enable", or "auto". Sensors that require motion effects (for example, lidar, radar, acoustic, rolling-shutter camera) should use "auto" or "enable".

Config-entry helper

Description

ovrtx_config_entry_log_file_path()

Write renderer logs to a file.

ovrtx_config_entry_log_level()

Set log verbosity.

ovrtx_config_entry_binary_package_root_path()

Point to a custom binary package root. May include OVX_CONFIG_EXECUTABLE_DIR_TOKEN to anchor the path at the running executable’s directory.

ovrtx_config_entry_keep_system_alive()

Keep shared graphics resources alive after the last renderer.

ovrtx_config_entry_active_cuda_gpus()

Restrict renderer-level CUDA-visible devices.

ovrtx_config_entry_use_vulkan()

Select the Vulkan backend where supported.

ovrtx_config_entry_motion_bvh()

Motion BVH mode: OVRTX_MOTION_BVH_DISABLE (default), OVRTX_MOTION_BVH_ENABLE, or OVRTX_MOTION_BVH_AUTO.

Renderer-level active_cuda_gpus must be compatible with any per-RenderProduct deviceIds allow-list. Refer to RenderProduct Device Pinning.

Runtime Package Layout#

With dynamic linking, ovrtx expects the binary package bin layout to stay together next to libovrtx-dynamic.so or ovrtx-dynamic.dll. The runtime package includes directories such as cache, library, libs, mdl, plugins, rendering-data, and usd_plugins.

Set binary_package_root_path only when static linking ovrtx or when a custom deployment layout separates the loader library from the package directories. When the package bin/ directory lives next to your executable, pass OVX_CONFIG_EXECUTABLE_DIR_TOKEN ("${executable_dir}") instead of resolving the executable directory in client code.

Multi-Renderer Processes#

For processes that create and destroy multiple renderers, initialize ovrtx once up front when using the C API, then shut it down once all renderers are gone.

// The STATIC ovrtx loader resolves the ${executable_dir} token to the running
// executable's directory; ovrtx_setup_runtime() links the package bin beside
// the exe as "ovrtx/", which we hand the loader as the binary package root.
ovx_string_t ovrtx_package_root = {
    OVX_CONFIG_EXECUTABLE_DIR_TOKEN "/ovrtx",
    sizeof(OVX_CONFIG_EXECUTABLE_DIR_TOKEN "/ovrtx") - 1};
ovrtx_config_entry_t ovrtx_config_entries[] = {
    ovrtx_config_entry_binary_package_root_path(ovrtx_package_root),
    ovrtx_config_entry_selection_outline_enabled(true),
    ovrtx_config_entry_selection_outline_width(4),
    ovrtx_config_entry_selection_fill_mode(OVRTX_SELECTION_FILL_MODE_GROUP_FILL_COLOR),
};
ovrtx_config_t ovrtx_config = {};
ovrtx_config.entries = ovrtx_config_entries;
ovrtx_config.entry_count =
    sizeof(ovrtx_config_entries) / sizeof(ovrtx_config_entries[0]);

// Publish OVRTX's USD plugin paths before ovstage initializes the shared USD
// runtime. The process-wide USD schema registry only discovers plugins once.
ovrtx_result_t result = ovrtx_register_schema_paths(&ovrtx_config);
if (check_and_print_error(result, "register_schema_paths")) {
    return 1;
}

// The STATIC ovstage loader resolves ${executable_dir} the same way ovrtx does.
// ovstage_setup_runtime() links the package bin beside the exe as "ovstage/"
// (next to "ovrtx/").
ovx_string_t ovstage_package_root = {
    OVX_CONFIG_EXECUTABLE_DIR_TOKEN "/ovstage",
    sizeof(OVX_CONFIG_EXECUTABLE_DIR_TOKEN "/ovstage") - 1};
ovstage_config_entry_t stage_config_entries[] = {
    ovstage_config_entry_binary_package_root_path(ovstage_package_root),
};
ovstage_config_t stage_config {};
stage_config.entries = stage_config_entries;
stage_config.entry_count = sizeof(stage_config_entries) / sizeof(stage_config_entries[0]);
ovstage_api_status_t stage_init_status = ovstage_initialize(&stage_config);
if (stage_init_status != OVSTAGE_OK) {
    print_ovstage_error(nullptr, stage_init_status, "initialize");
    return 1;
}

result = ovrtx_create_renderer(&ovrtx_config, &renderer);
if (check_and_print_error(result, "create_renderer")) {
    ovstage_shutdown();
    return 1;
}

auto cleanup = [&](int exit_code) {
    int result_code = exit_code;
    if (camera_query != OVSTAGE_INVALID_QUERY_HANDLE) {
        if (wait_ovstage_op(stage,
                            ovstage_release_query(stage, camera_query),
                            "release_query")) {
            result_code = 1;
        }
        camera_query = OVSTAGE_INVALID_QUERY_HANDLE;
    }
    if (stage_attached) {
        result = ovrtx_detach_ovstage(renderer);
        if (check_and_print_error(result, "detach_ovstage")) {
            result_code = 1;
        }
        stage_attached = false;
    }
    if (stage) {
        ovstage_api_status_t stage_result = ovstage_destroy_instance(stage);
        if (stage_result != OVSTAGE_OK) {
            print_ovstage_error(stage, stage_result, "destroy_instance");
            result_code = 1;
        }
        stage = nullptr;
    }
    if (renderer) {
        result = ovrtx_destroy_renderer(renderer);
        if (check_and_print_error(result, "destroy_renderer")) {
            result_code = 1;
        }
        renderer = nullptr;
    }
    // Release the ovstage static loader (unloads ovstage.dll). Safe even if
    // ovstage_initialize failed or was never reached.
    ovstage_shutdown();
    return result_code;
};

// Register the per-group outline/fill style before any picking happens.
if (!configure_selection_style(renderer)) {
    return cleanup(1);
}

On Linux headless systems, repeatedly creating and destroying renderers can force shared graphics resources to unload and reload. If that causes native driver crashes, configure keep_system_alive=True and initialize ovrtx before creating renderers. If the issue persists, set VK_LOADER_DISABLE_DYNAMIC_LIBRARY_UNLOADING=1 in the process environment.

Cleanup#

Python releases renderer resources when the Renderer object is destroyed. In C, cleanup is explicit:

// Unmap output
ovrtx_cuda_sync_t no_sync = {};
result = ovrtx_unmap_render_var_output(
    renderer, rendered_output.map_handle, no_sync);
if (check_and_print_error(result, "unmap_render_var_output")) {
    ovrtx_destroy_results(renderer, step_result_handle);
    return cleanup(1);
}

// Clean up resources (ovrtx will warn if results are leaked)
result = ovrtx_destroy_results(renderer, step_result_handle);
if (check_and_print_error(result, "destroy_results")) {
    return cleanup(1);
}