Skip to content

Commit 578e63c

Browse files
Single instance loader with forward compatibility
1 parent 9b9f2e4 commit 578e63c

File tree

14 files changed

+541
-1174
lines changed

14 files changed

+541
-1174
lines changed

specification/loader/api_layer.adoc

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ cooperate to ensure the correct sequencing of calls from one entity to the
1515
next. This group effort for call chain sequencing is hereinafter referred to as
1616
*distributed dispatch*.
1717

18+
If an API layer does not wish to intercept a command, it must forward the
19+
request made to its `xrGetInstanceProcAddr` implementation (provided through
20+
pname:getInstanceProcAddr) down to the next `xrGetInstanceProcAddr`
21+
implementation in the call chain (provided to the API layer through
22+
pname:nextGetInstanceProcAddr).
23+
1824
In distributed dispatch each API layer is responsible for properly calling the next
1925
entity in the call chain. This means that a dispatch mechanism is required for
20-
all OpenXR commands that an API layer intercepts. If an OpenXR command is not
21-
intercepted by an API layer, or if an API layer chooses to terminate the function by not
22-
calling down the chain, then no dispatch is needed for that particular function.
26+
all OpenXR commands that an API layer intercepts.
2327

2428
For example, if the enabled API layers intercepted only certain functions,
2529
the call chain would look as follows:
@@ -872,7 +876,7 @@ struct XrApiLayerCreateInfo {
872876
* pname:structVersion is the version of the structure being supplied by the
873877
loader.
874878
* pname:structSize is the size of the structure supplied by the loader.
875-
* pname:loaderInstance is the `XrInstance` used by the loader.
879+
* pname:loaderInstance is deprecated and must be ignored.
876880
* pname:settings_file_location is the location of any API layer settings file
877881
that can be used. This is currently unused.
878882
* pname:nextInfo is a pointer to the slink:XrApiLayerNextInfo structure which

specification/loader/application.adoc

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,6 @@ which could potentially increase performance. In that case, most commands
124124
would use the following call chain:
125125

126126
image::images/app_dispatch_table_call_chain.svg[align="center", title="Call Chain For Application Dispatch"]
127-
128-
Instance commands (i.e. those commands taking an `XrInstance` as its primary
129-
parameter), will always require a trampoline because they need to convert
130-
the instance to access the appropriate dispatch table, and to pass down the
131-
`XrInstance` value that was actually created in the API layers/runtime below it.
132127

133128

134129
[[openxr-loader-library-name]]
@@ -142,7 +137,7 @@ common OSs:
142137
|====
143138
| Operating System | Loader Library Name
144139
| Windows
145-
| openxr-loader-<major>_<minor>.lib/.dll
140+
| openxr-loader.lib/.dll
146141
| Linux
147142
| libopenxr_loader.so.<major>
148143
|====

specification/loader/design.adoc

Lines changed: 21 additions & 248 deletions
Original file line numberDiff line numberDiff line change
@@ -242,12 +242,7 @@ static XrResult ApiLayerInterface::LoadApiLayers(
242242
==== The LoaderInstance Class ====
243243

244244
The primary OpenXR object is the `XrInstance`, and from that most other data
245-
is either queried or created. In many ways, the `XrInstance` object is a
246-
loader object that manages all the other underlying OpenXR objects/handles.
247-
Because of this, a large amount of data needs to be associated with it by
248-
the loader.
249-
In order to do this, the loader maps an internal `LoaderInstance` object
250-
pointer to the returned `XrInstance` value.
245+
is either queried or created.
251246

252247
A `LoaderInstance` is created during the OpenXR `xrCreateInstance` call, and
253248
destroyed during the `xrDestroyInstance` call.
@@ -258,10 +253,19 @@ During `xrCreateInstance` the loader code calls
258253
[source,c++]
259254
----
260255
static XrResult LoaderInstance::CreateInstance(
261-
std::vector<std::unique_ptr<ApiLayerInterface>>& api_layer_interfaces,
262-
const XrInstanceCreateInfo* info,
263-
XrInstance* instance);
264-
----
256+
PFN_xrGetInstanceProcAddr get_instance_proc_addr_term,
257+
PFN_xrCreateInstance create_instance_term,
258+
PFN_xrCreateApiLayerInstance create_api_layer_instance_term,
259+
std::vector<std::unique_ptr<ApiLayerInterface>> layer_interfaces,
260+
const XrInstanceCreateInfo* createInfo,
261+
std::unique_ptr<LoaderInstance>* loader_instance);
262+
----
263+
* pname:get_instance_proc_addr_term is the function pointer to the
264+
terminator for xrGetInstanceProcAddr.
265+
* pname:create_instance_term is the function pointer to the
266+
terminator for xrCreateInstance.
267+
* pname:create_api_layer_instance_term is the function pointer to the
268+
terminator for xrCreateApiLayerInstance.
265269
* pname:api_layer_interfaces is a vector that contains a unique_ptr to all
266270
`ApiLayerInterface` objects that are valid and enabled. All of these
267271
pointers will be moved to the `LoaderInstance` on successful completion
@@ -279,8 +283,8 @@ work:
279283
`xrGetInstanceProcAddr` that passes through all enabled API layers and the
280284
runtime.
281285
* Create the instance using the generated `xrCreateInstance` call chain.
282-
* Create a parallel `LoaderInstance*` associated with the returned
283-
`XrInstance` using a C++ "unordered_map"
286+
* Create a parallel `LoaderInstance` associated with the returned
287+
`XrInstance`.
284288
* Generate a top-level dispatch table containing all the supported commands.
285289
** This table is built by using the generated `xrGetInstanceProcAddr`
286290
call chain
@@ -564,82 +568,11 @@ they can be referenced in the `xr_loader_generated.cpp` source file which
564568
is used for `xrGetInstanceProcAddr` as well as setting up the loader
565569
dispatch table.
566570

567-
Also included is a utility function prototype that will initialize a
568-
`LoaderInstance` dispatch table:
569-
570-
[[LoaderGenInitInstanceDispatchTable]]
571-
[source,c++]
572-
----
573-
void LoaderGenInitInstanceDispatchTable(
574-
XrInstance runtime_instance,
575-
std::unique_ptr<XrGeneratedDispatchTable>& table);
576-
----
577-
* pname:runtime_instance is the `XrInstance` returned by the runtime
578-
on the related call to fname:xrCreateInstance and that should be
579-
used to initialize pname:table.
580-
* pname:table is the dispatch table to initialize.
581-
582-
Additionally, this file contains extern prototypes for instances of the
583-
`HandleLoaderMap<>` class template, containing mainly an `unordered_map`
584-
and a `mutex`, that are used for storing the created OpenXR Object handles
585-
and relating them to their parent `XrInstance`. There should be one
586-
`HandleLoaderMap<>` instance for each OpenXR Object Handle type. See the
587-
<<functional-flow, Functional Flow>> section for more information about
588-
the usage of these.
589-
590-
This file also contains the prototype of a function that is used to
591-
clean up the OpenXR Object unordered_maps entries related to a
592-
`LoaderInstance` that is being deleted:
593-
594-
[[LoaderCleanUpMapsForInstance]]
595-
[source,c++]
596-
----
597-
void LoaderCleanUpMapsForInstance(
598-
class LoaderInstance *instance);
599-
----
600-
* pname:loader_instance is a pointer to the `LoaderInstance`
601-
that is going away and needs all references removed from all the
602-
global state.
603-
604571
==== xr_loader_generated.cpp ====
605572

606573
The `xr_loader_generated.cpp` source file contains the implementation
607574
of all generated OpenXR trampoline functions.
608575

609-
For example, it contains the implementations of
610-
flink:LoaderGenInitInstanceDispatchTable and
611-
flink:LoaderCleanUpMapsForInstance.
612-
613-
It also includes a function automatically generated for the `ApiLayerInterface`
614-
class that will update a dispatch table for that class,
615-
`ApiLayerInterface`::fname:GenUpdateInstanceDispatchTable. The function
616-
calls the API layer's version of `xrGetInstanceProcAddr` to populate the various
617-
entries in a `XrGeneratedDispatchTable`.
618-
619-
[source,c++]
620-
----
621-
void ApiLayerInterface::GenUpdateInstanceDispatchTable(
622-
XrInstance instance,
623-
std::unique_ptr<XrGeneratedDispatchTable>& table);
624-
----
625-
* pname:instance is the instance returned by the next component in the call-chain
626-
during fname:xrCreateInstance and is used with the next component's
627-
fname:xrGetInstanceProcAddr call.
628-
* pname:table is a reference to the table to update with the
629-
`ApiLayerInterface` 's API layer version of the fname:xrGetInstanceProcAddr.
630-
631-
Because this is an API layer, it will only populate the entry of a dispatch table if
632-
the API layer's `xrGetInstanceProcAddr` returns non-`NULL` for that command. This is
633-
because the loader calls this command to generate the top-level dispatch table
634-
when setting up the <<openxr-call-chains,call chain>>. The loader starts
635-
by creating a dispatch table pointing to the runtime commands. It then
636-
reverse increments through all enabled API layers, calling `ApiLayerInterface`::
637-
fname:GenUpdateInstanceDispatchTable for each API layer, which replaces only
638-
the commands that the specific API layer implements. This results in a
639-
dispatch table whose elements point to only the first implementation of each
640-
OpenXR command.
641-
642-
643576
[[manually-implemented-code]]
644577
=== Manually Implemented Code ===
645578

@@ -675,174 +608,14 @@ implemented in the loader.
675608
chain back to the standard `xrCreateInstance` path.
676609
|====
677610

678-
679611
[[functional-flow]]
680612
=== Functional Flow ===
681613

682-
The OpenXR loader makes all its decisions using OpenXR objects.
683-
The loader's main goal when dealing with OpenXR objects is to find the
684-
parent `LoaderInstance` for a given object.
685-
This parent `LoaderInstance` stores the dispatch table to use when calling any
686-
OpenXR command, so without the instance, the loader has no knowledge of where
687-
to send a command.
688-
This is because an application may create more than one `XrInstance`, with
689-
some of the instances having API layers enabled and some without.
690-
Without knowing which parent instance to use, the loader could end up calling
691-
an incorrect sequence of commands.
692-
693-
694-
==== Correlating an Object to Its Parent XrInstance ====
695-
696-
The loader automatic generated code tracks all OpenXR Object Handle types
697-
using an `HandleLoaderMap<>` containing an
698-
https://en.cppreference.com/w/cpp/container/unordered_map[unordered_map]
699-
and a mutex.
700-
Each type has it's own `HandleLoaderMap<>` associated with it.
701-
This `HandleLoaderMap<>` correlates an object against its parent `LoaderInstance` pointer.
702-
703-
[example]
704-
.HandleLoaderMap Correlating XrSession to LoaderInstance*
705-
====
706-
[source,c++]
707-
----
708-
HandleLoaderMap<XrSession> g_session_map;
709-
----
710-
====
711-
712-
Automatic code generation is used by capturing every `xrCreate` call,
713-
determining the parent object type and then determining the created object
714-
type.
715-
When triggered, the call first proceeds normally down the call chain.
716-
However, upon returning, an entry is created in this `HandleLoaderMap`.
717-
When a handle is received, the loader first looks up the parent
718-
`LoaderInstance` pointer using the `HandleLoaderMap` associated with the handle
719-
type.
720-
Once we have the parent object's `LoaderInstance*`, we create an entry using
721-
this value in our new object type's `HandleLoaderMap`.
722-
723-
[example]
724-
.HandleLoaderMap Creation examples
725-
====
726-
727-
Here's an example of how the automatically generated code looks for both
728-
accessing and creating an object which receives the `LoaderInstance*` as its
729-
parent.
730-
To determine the `LoaderInstance*` associated with the incoming `XrInstance`,
731-
you can see the lookup using the `g_instance_map` HandleLoaderMap.
732-
Then, a new value associates the recently created `XrSession` with the
733-
same `LoaderInstance*`.
734-
735-
[source,c++]
736-
----
737-
XRAPI_ATTR XrResult XRAPI_CALL xrCreateSession(
738-
XrInstance instance,
739-
const XrSessionCreateInfo* createInfo,
740-
XrSession* session) XRLOADER_ABI_TRY {
741-
742-
LoaderInstance *loader_instance = g_instance_map.Get(instance);
743-
if (nullptr == loader_instance) {
744-
LoaderLogger::LogValidationErrorMessage(
745-
"VUID-xrCreateSession-instance-parameter", "xrCreateSession",
746-
"instance is not a valid XrInstance",
747-
{XrSdkLogObjectInfo{instance, XR_OBJECT_TYPE_INSTANCE}});
748-
return XR_ERROR_HANDLE_INVALID;
749-
}
750-
const std::unique_ptr<XrGeneratedDispatchTable> &dispatch_table =
751-
loader_instance->DispatchTable();
752-
XrResult result = XR_SUCCESS;
753-
result = dispatch_table->CreateSession(instance, createInfo, session);
754-
if (XR_SUCCESS == result && nullptr != session) {
755-
XrResult insert_result =
756-
g_session_map.Insert(*session, *loader_instance);
757-
if (XR_FAILED(insert_result)) {
758-
LoaderLogger::LogErrorMessage("xrCreateSession",
759-
"Failed inserting new session into "
760-
"map: may be null or not unique");
761-
}
762-
}
763-
return result;
764-
}
765-
XRLOADER_ABI_CATCH_FALLBACK
766-
----
767-
====
768-
769-
Of course, all this is cleaned up when the appropriate `xrDestroy` command
770-
is called.
771-
772-
[example]
773-
====
774-
[source,c++]
775-
----
776-
XRAPI_ATTR XrResult XRAPI_CALL xrDestroySession(
777-
XrSession session)
778-
XRLOADER_ABI_TRY {
779-
LoaderInstance *loader_instance = g_session_map.Get(session);
780-
// Destroy the mapping entry for this item if it was valid.
781-
if (nullptr != loader_instance) {
782-
g_session_map.Erase(session);
783-
}
784-
if (nullptr == loader_instance) {
785-
LoaderLogger::LogValidationErrorMessage(
786-
"VUID-xrDestroySession-session-parameter", "xrDestroySession",
787-
"session is not a valid XrSession",
788-
{XrSdkLogObjectInfo{session, XR_OBJECT_TYPE_SESSION}});
789-
return XR_ERROR_HANDLE_INVALID;
790-
}
791-
const std::unique_ptr<XrGeneratedDispatchTable> &dispatch_table =
792-
loader_instance->DispatchTable();
793-
XrResult result = XR_SUCCESS;
794-
result = dispatch_table->DestroySession(session);
795-
return result;
796-
}
797-
XRLOADER_ABI_CATCH_FALLBACK
798-
----
799-
====
800-
801-
If a call to `xrDestroyInstance` is made before any objects related to the
802-
`XrInstance` have been destroyed, they are still properly cleaned up by the
803-
loader.
804-
This is because the loader's `xrDestroyInstance` command calls
805-
flink:LoaderCleanUpMapsForInstance to clean up all unordered_maps entries
806-
associated with the instance before it's destroyed.
807-
808-
809-
==== Protecting the Unordered_Map Content ====
810-
811-
In order to properly protect the contents of the unordered_map in a
812-
multithreading scenario, we use a std::mutex per unordered_map to
813-
control when the loader writes to, or reads from, the unordered_map.
814-
815-
In general, use of these mutexes is handled automatically by the
816-
`HandleLoaderMap<>` class template.
817-
818-
==== Potential Problems ====
819-
820-
The OpenXR loader could run into issues, even with this design, under
821-
certain scenarios:
822-
823-
.Loader Problem Scenario 1
824-
825-
If an application creates its own OpenXR command dispatch table, but still
826-
also uses OpenXR commands exported directly from the loader, we could see an
827-
issue. The issue arises if the application calls `xrCreate` for an object
828-
using the dispatch table it generated. This would cause the `xrCreate`
829-
command to bypass the loader's trampoline call (which is where the
830-
unordered_map behavior exists). In this case, the loader would not create an
831-
entry in the unordered_map. The problem becomes obvious if the user calls a
832-
loader export which uses that command. Now, the loader has no way of finding
833-
the parent instance. The loader could make a logical guess, but it may choose
834-
the wrong parent instance if more than one has been created.
835-
836-
[WARNING]
837-
.TODO (i/761)
838-
====
839-
* What do we do here?
840-
** I suggest we disallow mixing and matching using loader exports with
841-
application generated dispatch tables.
842-
** At least where commands could take a different path for `xrCreate`
843-
and usage.
844-
====
845-
614+
The loader supports a single XrInstance at a time in order to avoid
615+
tracingk handle values and their relationship to the `LoaderInstance`. Every
616+
XR function call is assumed to be for the single XrInstance that has been
617+
created. This enables the loader to work with future extensions and handle types
618+
without change.
846619

847620
[[platform-specific-behavior]]
848621
=== Platform-Specific Behavior ===

specification/loader/overview.adoc

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ that includes each function selected by the layer. At `xrCreateInstance` time th
138138
loader initializes all enabled API layers and creates call
139139
chains for each OpenXR command, with each entry of the resulting dispatch table
140140
pointing to the first element of that chain. The loader builds an individual
141-
dispatch table for each `XrInstance` that is created.
141+
dispatch table for the `XrInstance` that is created.
142142

143143
When an application calls an OpenXR command, this typically will first hit a
144144
_trampoline_ function in the loader. These _trampoline_ functions are small,
@@ -149,7 +149,5 @@ process data before proceeding to the appropriate runtime.
149149

150150
image::images/call_chain_example.svg[align="center", title="Example Call Chain"]
151151

152-
The loader associates all OpenXR objects with their parent `XrInstance` on
153-
creation. For OpenXR API commands which don't include an `XrInstance` explicitly,
154-
the loader uses the stored parent information to identify the appropriate
155-
call chain to be dispatched.
152+
The loader only allows a single outstanding `XrInstance` and uses the generated
153+
dispatch table for that `XrInstance` for all OpenXR API commands.

src/api_layers/api_dump.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -454,8 +454,7 @@ XrResult ApiDumpLayerXrCreateApiLayerInstance(const XrInstanceCreateInfo *info,
454454
// Validate the API layer info and next API layer info structures before we try to use them
455455
if (nullptr == apiLayerInfo || XR_LOADER_INTERFACE_STRUCT_API_LAYER_CREATE_INFO != apiLayerInfo->structType ||
456456
XR_API_LAYER_CREATE_INFO_STRUCT_VERSION > apiLayerInfo->structVersion ||
457-
sizeof(XrApiLayerCreateInfo) > apiLayerInfo->structSize || nullptr == apiLayerInfo->loaderInstance ||
458-
nullptr == apiLayerInfo->nextInfo ||
457+
sizeof(XrApiLayerCreateInfo) > apiLayerInfo->structSize || nullptr == apiLayerInfo->nextInfo ||
459458
XR_LOADER_INTERFACE_STRUCT_API_LAYER_NEXT_INFO != apiLayerInfo->nextInfo->structType ||
460459
XR_API_LAYER_NEXT_INFO_STRUCT_VERSION > apiLayerInfo->nextInfo->structVersion ||
461460
sizeof(XrApiLayerNextInfo) > apiLayerInfo->nextInfo->structSize ||

src/common/platform_utils.hpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ static inline std::string PlatformUtilsGetEnv(const char* name) {
242242
const std::wstring wname = utf8_to_wide(name);
243243
const DWORD valSize = ::GetEnvironmentVariableW(wname.c_str(), nullptr, 0);
244244
// GetEnvironmentVariable returns 0 when environment variable does not exist or there is an error.
245-
if (valSize == 0) {
245+
// The size includes the null-terminator, so a size of 1 is means the variable was explicitly set to empty.
246+
if (valSize == 0 || valSize == 1) {
246247
return {};
247248
}
248249

0 commit comments

Comments
 (0)