C API Practical Patterns#
The C API reference lists functions and types. This page covers the common usage patterns that make those functions safe to combine in applications.
Version and Configuration#
Build the renderer configuration from ovrtx_config_entry_t values, then pass
the array to ovrtx_create_renderer().
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);
String Handling#
ovrtx C strings use ovx_string_t: a ptr and explicit length. The
strings are null-terminated where practical, but C and C++ code should use the
length field for printing, comparison, and copying.
The minimal C example’s error helper demonstrates safe conversion to
std::string_view and length-aware printing:
template <typename ResultT>
static bool check_and_print_error(ResultT const& result,
std::string_view operation) {
if (result.status == OVRTX_API_ERROR) {
ovx_string_t error = ovrtx_get_last_error();
if (error.ptr && error.length > 0) {
std::cerr << "ovrtx " << operation << " failed: "
<< std::string_view(error.ptr, error.length) << std::endl;
} else {
std::cerr << "ovrtx " << operation << " failed" << std::endl;
}
return true;
}
return false;
}
Error Lifetimes#
Immediate API failures report details through ovrtx_get_last_error().
Consume or copy the returned string before the next API call on the same thread.
const void* vtable = nullptr;
ovrtx_result_t result = ovrtx_query_extension("ovrtx.docs.missing_extension", &vtable);
ASSERT_EQ(result.status, OVRTX_API_ERROR);
ovx_string_t error = ovrtx_get_last_error();
std::string message(error.ptr, error.length);
Asynchronous failures are reported when waiting. The wait result contains op ids
that failed, and each id can be queried with ovrtx_get_last_op_error().
Those wait-result arrays and strings are transient and invalidated by the next
wait on the same thread.
// Wait for the op. The new 0.3.0 shape of ovrtx_op_wait_result_t carries:
// - error_op_ids[0..num_error_ops) : ids that errored since the last wait
// - lowest_pending_op_id : 0 if everything is resolved
// Both error_op_ids and the strings returned by ovrtx_get_last_op_error()
// are thread-local and are invalidated by the next wait on this thread.
ovrtx_op_wait_result_t wait_result{};
ovrtx_result_t r = ovrtx_wait_op(renderer_, eq.op_index, ovrtx_timeout_infinite, &wait_result);
ASSERT_API_SUCCESS(r.status);
ASSERT_GT(wait_result.num_error_ops, 0u) << "expected the failing op to be reported";
for (size_t i = 0; i < wait_result.num_error_ops; ++i) {
ovx_string_t err = ovrtx_get_last_op_error(wait_result.error_op_ids[i]);
// Prefer the explicit length field over the null terminator.
ASSERT_GT(err.length, 0u);
printf("op %llu error: %.*s\n",
(unsigned long long)wait_result.error_op_ids[i],
(int)err.length, err.ptr);
}
// Nothing is still in flight → lowest_pending_op_id is 0.
EXPECT_EQ(wait_result.lowest_pending_op_id, 0u);
No explicit error-release call is needed.
// In 0.3.0 the per-thread error data is transient: it lives until the next
// ovrtx_wait_op() on the same thread, which implicitly recycles it. There is
// no ovrtx_release_errors() call to make anymore.
std::string bogus = get_docs_test_data_dir() + "/another-missing-file.usda";
ovrtx_enqueue_result_t eq = ovrtx_open_usd_from_file(renderer_, {bogus.c_str(), bogus.size()});
ASSERT_API_SUCCESS(eq.status);
ovrtx_op_wait_result_t wr1{};
ASSERT_API_SUCCESS(ovrtx_wait_op(renderer_, eq.op_index, ovrtx_timeout_infinite, &wr1).status);
ASSERT_GT(wr1.num_error_ops, 0u);
// Do another wait on the same thread — this invalidates wr1's error_op_ids.
// No explicit release step is required or supported.
ovrtx_enqueue_result_t eq2 = ovrtx_reset_stage(renderer_);
ASSERT_API_SUCCESS(eq2.status);
ovrtx_op_wait_result_t wr2{};
ASSERT_API_SUCCESS(ovrtx_wait_op(renderer_, eq2.op_index, ovrtx_timeout_infinite, &wr2).status);
ASSERT_NO_OP_ERRORS(wr2);
Status Queries#
Status queries are snapshots for pending or recently completed operations. Use
them for progress indicators and diagnostics. Release each successful status
query with ovrtx_release_op_status().
ovrtx_op_status_t status{};
ASSERT_API_SUCCESS(ovrtx_query_op_status(renderer_, eq.op_index, &status).status);
ASSERT_EQ(status.op_id, eq.op_index);
ASSERT_TRUE(status.state == OVRTX_EVENT_PENDING ||
status.state == OVRTX_EVENT_COMPLETED);
ASSERT_TRUE(status.progress < 0.0 || (status.progress >= 0.0 && status.progress <= 1.0));
for (size_t i = 0; i < status.counter_count; ++i) {
ASSERT_NE(status.counters[i].name.ptr, nullptr);
ASSERT_GT(status.counters[i].name.length, 0u);
if (status.counters[i].total != 0u) {
ASSERT_LE(status.counters[i].current, status.counters[i].total);
}
}
ASSERT_API_SUCCESS(ovrtx_release_op_status(renderer_, &status).status);
Logging Callback#
The log callback is process-global. The channel filter is a comma-separated
list of channel_prefix=level rules; the longest matching channel prefix
wins. Accepted levels include verbose, debug, info, warn,
warning, error, and fatal.
// First pass: receive all messages (NULL channel filter).
g_message_count.store(0);
ovrtx_result_t r = ovrtx_set_log_callback(OVRTX_LOG_INFO,
nullptr, // NULL = all channels
&count_messages,
&g_message_count);
ASSERT_API_SUCCESS(r.status);
// Reset first so the renderer is in a clean state.
ovrtx_enqueue_result_t eq = ovrtx_reset_stage(renderer_);
ASSERT_API_SUCCESS(eq.status);
ovrtx_op_wait_result_t wait_result;
ASSERT_API_SUCCESS(ovrtx_wait_op(renderer_, eq.op_index, ovrtx_timeout_infinite, &wait_result).status);
do_open_usd();
// Ensure pending log messages have been delivered through the callback.
ovrtx_timeout_t flush_timeout{5'000'000'000ull};
ovrtx_flush_log(flush_timeout);
int observed_any_channel = g_message_count.load();
// Second pass: install a high default threshold and an explicit low
// threshold for a channel prefix that cannot match any real channel.
g_message_count.store(0);
std::string bogus = "this.channel.does.not.exist.42=info";
ovx_string_t filter{bogus.c_str(), bogus.size()};
r = ovrtx_set_log_callback(OVRTX_LOG_FATAL, &filter, &count_messages, &g_message_count);
ASSERT_API_SUCCESS(r.status);
// Re-run the same work and flush — the callback should not fire.
eq = ovrtx_reset_stage(renderer_);
ASSERT_API_SUCCESS(eq.status);
ASSERT_API_SUCCESS(ovrtx_wait_op(renderer_, eq.op_index, ovrtx_timeout_infinite, &wait_result).status);
do_open_usd();
ovrtx_flush_log(flush_timeout);
int observed_bogus_filter = g_message_count.load();
// Disable the callback before the renderer is torn down.
ovrtx_set_log_callback(OVRTX_LOG_INFO, nullptr, nullptr, nullptr);
Resource Cleanup#
Destroy renderers with
ovrtx_destroy_renderer().Destroy fetched step results with
ovrtx_destroy_results().Unmap render-var outputs with
ovrtx_unmap_render_var_output().Destroy attribute bindings with
ovrtx_destroy_attribute_binding().Release operation status snapshots with
ovrtx_release_op_status().