Repository navigation
rtx: Allow multiple MDL-capable ANARI devices in one process - #384
Merged
jeffamstutz merged 2 commits intoOct 29, 2026
Merged
Conversation
mi_factory hands out at most one live INeuray per loaded library image, and dlopen of the same path returns the same refcounted handle. Each libmdl::Core acquired its own instance, so creating a second VisRTX device while a first was alive got a null INeuray, threw, and reported "Failed initializing VisRTX MDL support." Introduce detail::SharedNeuray, owning the library image and the INeuray, held by every Core through a shared_ptr handed out via a static weak_ptr and mutex. The SDK is shut down and unloaded only when the last Core dies, so destroying every device and creating a new one still reacquires a fresh instance. Teardown is serialized against a replacement: the weak_ptr expires before the destructor has unloaded the image, and a concurrent acquire that reloaded it meanwhile would overlap loadLibrary/freeLibrary. The destructor cannot simply take the mutex, because the Core constructor's scope_fail destroys the object while already holding it. The SDK accepts a single receiving logger per instance, so a shared instance has one master logger: the first Core in installs the sink it was given and later ones leave it alone. SDK-internal diagnostics reach that master whichever device provoked them. Messages VisRTX logs itself never pass through the SDK's logger, so they still reach the device they belong to. Attributing SDK diagnostics per device is possible -- route through a shim consulting a thread-local "active device" -- and an earlier revision did, but it cost a routing logger, a scope guard on all sixteen SDK entry points and self-reference guards for instances handed back in, to redirect a trickle of compiler warnings that no application can act on programmatically. An application that needs precise attribution configures its own INeuray and hands it over, which makes the question moot. Recorded in the doc as a deliberate trade rather than an oversight. Devices from another vendor in the same process cannot see VisRTX's internals, so sharing with them is mediated by the application. Expose the instance as an `ineuray` device property to pair with the existing `ineuray` parameter, and describe both -- plus mdlSearchPaths and mdlResourceSearchPaths -- in VISRTX_MATERIAL_MDL so the contract is discoverable rather than folklore. docs/mdl-instance-sharing.md covers the handoff and why an automatic rendezvous was prototyped and rejected. The ANARI_DEVICE entry in the extension JSON deliberately carries no "name": a named subtype makes the query generator emit a subtype-dispatching ANARI_DEVICE_info, and callers pass NULL for the device subtype (visrtx_extensions.h), which then crashes in subtype_hash. Properties are not introspectable at all -- the generator's info vocabulary has no "property" entry, which is why the core spec's own geometryMaxIndex is absent too -- so TestMdlIneurayIntrospect verifies the property by reading it. Tests: TestMdlMultiDevice (overlapping devices, sequential create/destroy, concurrent creation), TestMdlIneurayHandoff (end-to-end property-to-parameter handoff) and TestMdlIneurayIntrospect, plus TestMdlLogging pinning both halves of the logging contract. The first three were confirmed to fail without the fix. Review follow-ups in this commit: the teardown announcement moved from the destructor into the shared_ptr's deleter, because the weak_ptr expires at the last strong decrement and a flag set inside the destructor leaves a window in which a concurrent acquire sees an expired weak_ptr, no announced teardown, and reloads the image while the old instance is still alive (reproduced 197/200 in a model of the ordering); a failed INeuray::start() is no longer published as a successful acquisition; and the self-reference hazard in the adopt path disappeared with the router itself.
MDL search paths are a property of the INeuray instance, and every device in a process shares one. IMdl_configuration offers only add_mdl_path and clear_mdl_paths -- no way to withdraw a single root or to add one on behalf of a particular owner -- so a device applying its own `mdlSearchPaths` had to clear first and silently dropped every other device's roots. A device updating its own parameter does want its previous roots retired, so neither "always clear" nor "never clear" is correct. detail::SearchPathRegistry keeps each Core's contribution separately, keyed by instance and then by owner, and rebuilds the instance's list as their union on every change. Setting one device's roots leaves the others' in place; re-setting the same device's parameter replaces only that device's contribution; destroying a device withdraws it. Duplicates are added once, and the system and user paths are re-appended after every rebuild since the clear removes those too. Keyed on the INeuray rather than on VisRTX's shared-instance object, so it equally covers devices that adopted an application-supplied instance through the `ineuray` parameter -- previously those bypassed the union entirely and cleared the shared list outright. Withdrawing a root does not unload a module: one already loaded stays in the MDL database and keeps resolving by name whatever the search paths later say (verified against the SDK). TestMdlSearchPaths therefore asserts that new roots start resolving rather than that retired ones stop. It writes a module per directory, each emitting on a different channel, and loads them by module name so resolution has to come off the search path; the rendered pixel identifies which module was found and a black pixel means the root was lost. It covers three concurrent devices with disjoint roots, devices leaving, a device updating its own roots, and a device updating them while another is live. The last case fails without this change. docs/mdl-search-paths.md records the constraint and the module-caching caveat. Review follow-ups in this commit: forget() rebuilds before erasing the last owner's entry, so a departed owner's roots do not stay installed on an instance that outlives it; both path kinds stringify with generic_string() so de-duplication is consistent on Windows; and the docs and JSON now say the union covers participating VisRTX devices rather than every user of a shared instance, and that precedence between conflicting roots is unspecified.
jeffamstutz
approved these changes
Oct 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
mi_factoryyields at most one liveINeurayper loaded MDL SDK image. Eachlibmdl::Coreasked for its own, so the second one to ask gotnullptrandreported
Failed initializing VisRTX MDL support.anariLoadLibrary("visrtx"), a device eachHow. A process-wide refcounted
SharedNeurayowns the image and theinstance; the last
Coreout shuts it down and unloads. That covers everythingsharing one VisRTX binary. Separate binaries can't see each other's statics, so
there the application reads a new
ineuraydevice property and sets it on theexisting
ineurayparameter — both now declared inVISRTX_MATERIAL_MDL.devices/rtx/docs/mdl-instance-sharing.md describes the MDL neuray instance sharing/hand off process.
Second commit: search paths belong to the
INeuray, not the device, and theSDK only offers clear-and-rebuild — so one device setting its roots dropped
every other device's. Now unioned per owner.