1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867
|
// Copyright (c) 2017-2021, The Khronos Group Inc.
//
// SPDX-License-Identifier: CC-BY-4.0
[[loader-design]]
== Loader Design ==
This section of the document is focused on the internal design of the OpenXR
loader provided in the `src/loader` folder.
The OpenXR loader is composed of several classes.
The overall class diagram looks roughly like the following:
image::images/class_diagram.svg[align="center", title="Loader Class Diagram"]
[[class-roles]]
=== Class Roles ===
==== Manifest File Classes ====
The Desktop OpenXR loader uses JSON manifest files for information about
available <<api-layer-manifest-file-format, API layers>> and
<<runtime-manifest-file-format, runtimes>>.
All the functionality for finding, parsing, and processing the various
manifest file formats are found in the three manifest file classes:
`ManifestFile`, `RuntimeManifestFile`, and `ApiLayerManifestFile`.
All three classes are defined in the `manifest_file` header (.hpp) and
source (.cpp) files.
[[manifestfile]]
.ManifestFile
`ManifestFile` is the base class containing the majority of common code.
It only performs a minimal set of validation and provides accessor
functions.
These functions perform all the work using two primary sources:
* The std::experimental::filesystem pass:[C++] functionality exposed by most
modern compilers for finding the manifest files.
* The https://github.com/open-source-parsers/jsoncpp[JsonCPP] library for
processing and validating the manifest files once located.
[[runtimemanifestfile]]
.RuntimeManifestFile
`RuntimeManifestFile` is a derived class (with <<manifestfile,ManifestFile>>
as the parent) which handles specifically finding and parsing any
<<runtime-manifest-file-format, runtime manifest files>>.
It implements a static function fname:FindManifestFiles which is to be used
as a factory method for finding all available runtime manifest files,
creating an instance of `RuntimeManifestFiles` for each, and saving them if
they appear to be valid.
This is the command that any OpenXR command inside the loader should call if
it requires a list of available runtime manifest files.
[source,c++]
----
static XrResult RuntimeManifestFile::FindManifestFiles(
std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files);
----
* pname:manifest_files is a vector that will be used to store a unique_ptr
to each valid runtime manifest file found.
The `RuntimeManifestFile` also provides a utility function for creating an
instance of itself if it determines everything is valid inside the JSON
file.
fname:CreateIfValid not only validates the JSON format, but also verifies
that the specific fields required by the RuntimeManifestFile class are
available.
[source,c++]
----
void RuntimeManifestFile::CreateIfValid(
std::string filename,
std::vector<std::unique_ptr<RuntimeManifestFile>> &manifest_files);
----
* pname:filename indicates the absolute file name path to the manifest
file that needs to be loaded and verified.
* pname:manifest_files is a vector that will be used to store a unique_ptr
to each valid runtime manifest file found.
If this call determines the file exists and is valid, it will create an
instance of `RuntimeManifestFile` and add it to this vector.
[[apilayermanifestfile]]
.ApiLayerManifestFile
Similar to <<runtimemanifestfile,RuntimeManifestFile>>,
`ApiLayerManifestFile` is a derived class (with
<<manifestfile,ManifestFile>> as the parent).
This class handles specifically finding and parsing any
<<api-layer-manifest-file-format, API layer manifest files>>.
It also implements a static function fname:FindManifestFiles which is to be
used as a factory method for finding all available API layer manifest files,
creating an instance of `ApiLayerManifestFiles` for each, and saving them if
they appear to be valid.
This is the command that any OpenXR command inside the loader should call if
it requires a list of available API layer manifest files.
[source,c++]
----
static XrResult ApiLayerManifestFile::FindManifestFiles(
ManifestFileType type,
std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files);
----
* pname:type indicates the specific type of manifest file being searched
for.
In this case, it must: be either `MANIFEST_TYPE_IMPLICIT_API_LAYER` or
`MANIFEST_TYPE_EXPLICIT_API_LAYER`.
* pname:manifest_files is a vector that will be used to store a unique_ptr
to each valid API layer manifest file found.
The `ApiLayerManifestFile` also provides a utility function for creating an
instance of itself if it determines everything is valid inside the JSON
file.
fname:CreateIfValid not only validates the JSON format, but also verifies
that the specific fields required by the ApiLayerManifestFile class are
available.
[source,c++]
----
void ApiLayerManifestFile::CreateIfValid(
std::string filename,
std::vector<std::unique_ptr<ApiLayerManifestFile>> &manifest_files);
----
* pname:filename indicates the absolute file name path to the manifest
file that needs to be loaded and verified.
* pname:manifest_files is a vector that will be used to store a unique_ptr
to each valid runtime manifest file found.
If this call determines the file exists and is valid, it will create an
instance of `ApiLayerManifestFile` and add it to this vector.
==== Library Interface Classes ====
The OpenXR loader is responsible with interfacing with other libraries to
complete its tasks.
Typically, there will be some interface negotiation between these other
libraries and the loader (see <<loader-api-layer-interface-negotiation,
Loader/Layer Interface Negotiation>> or
<<loader-runtime-interface-negotiation, Loader/Runtime Interface
Negotiation>> for more information).
The Interface classes are responsible with using the contents of a
ManifestFile to setup the interface.
Each Interface class must be used with the appropriate ManifestFile type to
properly interface with a specific type of library.
[[runtimeinterface]]
.RuntimeInterface
The `RuntimeInterface` is tasked with working with runtime libraries.
Once the runtime manifest file information is found and parsed by the
<<runtimemanifestfile, RuntimeManifestFile>> class, it is passed along to
this class which will load the appropriate library and perform the required
<<loader-runtime-interface-negotiation, Loader/Runtime Interface
Negotiation>>.
The `RuntimeInterface` implements a static function fname:LoadRuntime which
is to be used as a factory method for loading the current active runtime,
performing the appropriate negotiation, and then creating an instance of
`RuntimeInterface`.
This is the command that any OpenXR command inside the loader should call if
it requires loading the runtime.
The command will only truly load the runtime library if it is not currently
loaded, and then keep a reference count of how many requests to load it have
been made.
[source,c++]
----
static XrResult RuntimeInterface::LoadRuntime();
----
Likewise, the `RuntimeInterface` implements a static function
fname:UnloadRuntime which is to be used to unload the current active
runtime.
This will reduce the reference count until it is 0 and then unload the
active runtime library.
[source,c++]
----
static XrResult RuntimeInterface::UnloadRuntime();
----
Finally, the `RuntimeInterface` implements a static function
fname:GetRuntime which is used to return the single instance of the
`RuntimeInterface` class.
[source,c++]
----
static RuntimeInterface& GetRuntime();
----
During `XrInstance` creation and destruction, the `RuntimeInterface` needs
to work directly with the runtime library.
To do this, the `RuntimeInterface` has two methods that are used by the
OpenXR loader:
[source,c++]
----
XrResult RuntimeInterface::CreateInstance(
const XrInstanceCreateInfo* info);
----
* pname:info is a pointer to the `XrInstanceCreateInfo` passed into the
OpenXR `xrCreateInstance` command that triggered this call.
Once successful completion occurs, the `RuntimeInterface` will:
* Store the runtime's version of the `XrInstance`
* Generate a dispatch table to all known OpenXR commands implemented by the
runtime.
To destroy a runtime's instance as part of the `xrDestroyInstance` command,
the loader calls fname:DestroyInstance;
[source,c++]
----
XrResult RuntimeInterface::DestroyInstance();
----
[[apilayerinterface]]
.ApiLayerInterface
Similarly, the `ApiLayerInterface` class is tasked with working with API
layer libraries.
Once the API layer manifest file information is found and parsed by the
<<apilayermanifestfile, ApiLayerManifestFile>> class, it is passed along to
this class which will load the appropriate library and perform the required
<<loader-api-layer-interface-negotiation, Loader/Layer Interface
Negotiation>>.
The `ApiLayerInterface` implements a static function fname:LoadApiLayers
which is to be used as a factory method for loading all available API
layers, performing the appropriate negotiation, and then creating an
instance of `ApiLayerInterface` for each.
This is the command that any OpenXR command inside the loader should call if
it requires load one or more API layers:
[source,c++]
----
static XrResult ApiLayerInterface::LoadApiLayers(
std::vector<std::unique_ptr<ApiLayerManifestFile>>& manifest_files,
std::vector<std::string> enabled_layers,
std::vector<std::unique_ptr<ApiLayerInterface>>& api_layer_interfaces);
----
* pname:manifest_files is a vector of unique_ptr elements containing the
loaded API layer manifest information.
The contents of this vector will be either transferred to a new
`ApiLayerInterface` object placed in the pname:api_layer_interfaces
vector, or deleted when the call to this method completes.
* pname:enabled_layers is a vector of names for all API layers that are
enabled by the environment or the user.
* pname:api_layer_interfaces is a vector that will be used to store a
unique_ptr to a `ApiLayerInterface` object representing each valid API
layer that is enabled and has completed loading and negotiation.
==== The LoaderInstance Class ====
The primary OpenXR object is the `XrInstance`, and from that most other data
is either queried or created.
A `LoaderInstance` is created during the OpenXR `xrCreateInstance` call, and
destroyed during the `xrDestroyInstance` call.
During `xrCreateInstance` the loader code calls
`LoaderInstance`::pname:CreateInstance factory method:
[[CreateInstance]]
[source,c++]
----
static XrResult LoaderInstance::CreateInstance(
PFN_xrGetInstanceProcAddr get_instance_proc_addr_term,
PFN_xrCreateInstance create_instance_term,
PFN_xrCreateApiLayerInstance create_api_layer_instance_term,
std::vector<std::unique_ptr<ApiLayerInterface>> layer_interfaces,
const XrInstanceCreateInfo* createInfo,
std::unique_ptr<LoaderInstance>* loader_instance);
----
* pname:get_instance_proc_addr_term is the function pointer to the
terminator for xrGetInstanceProcAddr.
* pname:create_instance_term is the function pointer to the terminator for
xrCreateInstance.
* pname:create_api_layer_instance_term is the function pointer to the
terminator for xrCreateApiLayerInstance.
* pname:api_layer_interfaces is a vector that contains a unique_ptr to all
`ApiLayerInterface` objects that are valid and enabled.
All of these pointers will be moved to the `LoaderInstance` on
successful completion of the `CreateInstance` call.
* pname:info is a pointer to the `XrInstanceCreateInfo` passed into the
OpenXR `xrCreateInstance` command that triggered this call.
* pname:instance contains a returned pointer to the `XrInstance` that will
be returned upon successful execution and associated with this
`LoaderInstance` object.
During the fname:CreateInstance call, the loader will perform the following
work:
* Generate the call chain for both `xrCreateInstance` and
`xrGetInstanceProcAddr` that passes through all enabled API layers and the
runtime.
* Create the instance using the generated `xrCreateInstance` call chain.
* Create a parallel `LoaderInstance` associated with the returned
`XrInstance`.
* Generate a top-level dispatch table containing all the supported commands.
** This table is built by using the generated `xrGetInstanceProcAddr` call
chain
Because the loader knows what runtime need to be called as part of the
create sequence, it inserts a terminator during the `xrCreateInstance`
sequence called `loaderXrTermCreateInstance` after the last API layer in
order to create the runtime instance.
==== Logging Classes ====
.LoaderLogger
The `LoaderLogger` class was created to provide global logging capability to
the OpenXR loader.
It was implemented as a Singleton to reduce the overhead of passing
pointers/references around to the various loader objects.
To get a reference to the `LoaderLogger` singleton, use the
fname:GetInstance method:
[source,c++]
----
static LoaderLogger& LoaderLogger::GetInstance();
----
The `LoaderLogger` works by sending all received messages to various
instances of <<loaderlogrecorder, LoaderLogRecorder> objects.
To add a `LoaderLogRecorder` to the `LoaderLogger`, call
fname:AddLogRecorder:
[source,c++]
----
void LoaderLogger::AddLogRecorder(
std::unique_ptr<LoaderLogRecorder>& recorder);
----
* pname:recorder is a unique_ptr to a create `LoaderLogRecorder` or
derived object.
Once added, general log messages will be passed to each of the
`LoaderLogRecorder` stored in an internal vector.
Any source inside of the loader may trigger a log message by using the
pname:LogMessage command:
[source,c++]
----
bool LoaderLogger::LogMessage(
XrLoaderLogMessageSeverityFlagBits message_severity,
XrLoaderLogMessageTypeFlags message_type,
const std::string& message_id,
const std::string& command_name
const std::string& message,
const std::vector<XrLoaderLogObjectInfo>& objects = {});
----
* pname:message_severity the severity of the message
* pname:message_type is type of the message
* pname:message_id is the message id, typically for loader messages this
is "OpenXR-Loader"
* pname:command_name is the name of the OpenXR command associated with the
message.
May be an empty string.
* pname:message is the message.
* pname:objects a vector of objects that are relevant to this message.
May be empty.
Because of the complex nature of that method, and the fact that most log
messages can be simplified, the OpenXR loader also supplies the following
static methods for logging:
[source,c++]
----
static bool LogErrorMessage(
const std::string& command_name,
const std::string& message,
const std::vector<XrLoaderLogObjectInfo>& objects = {});
static bool LogWarningMessage(
const std::string& command_name,
const std::string& message,
const std::vector<XrLoaderLogObjectInfo>& objects = {});
static bool LogInfoMessage(
const std::string& command_name,
const std::string& message,
const std::vector<XrLoaderLogObjectInfo>& objects = {});
static bool LogVerboseMessage(
const std::string& command_name,
const std::string& message,
const std::vector<XrLoaderLogObjectInfo>& objects = {});
----
* pname:command_name the OpenXR command that is related to the message.
May be an empty string.
* pname:message the message that needs to be logged
* pname:objects an optional array of OpenXR object handles that are
related to the log message.
It's important to note that these static methods also take care of grabbing
the `LoaderLogger`::fname:GetInstance() and making the appropriate call to
`LoaderLogger`::pname:LogMessage().
[example]
.Using the Log Messages
====
Here are a few examples of triggering a log message:
[source,c++]
----
XrResult res = xrCreateInstance(info, instance);
if (XR_SUCCESS != res) {
std::string error_message = "xrCreateInstance failed with result ";
error_message += std::to_string(res);
LoaderLogger::LogErrorMessage("", error_message);
}
----
[source,c++]
----
// After successfully adding all API layers
LoaderLogger::LogInfoMessage("", "Loaded all API layers");
----
In these examples, the message does not correspond to a named command so an
empty string is passed.
====
[[loaderlogrecorder]]
.LoaderLogRecorder
The `LoaderLogRecorder` is a base class that defines the basics used for
recording a log message somewhere.
`LoaderLogRecorder` provides no protections for multithreading logging.
Any required protections should be implemented by the derived class that is
multithread sensitive (i.e. if a logger wrote to a file).
The pure virtual method the base class defines that is used to record log
messages is the fname:LogMessage method.
Each derived class is responsible for defining exactly how log messages are
recorded.
[source,c++]
----
virtual bool LogMessage(
XrLoaderLogMessageSeverityFlagBits message_severity,
XrLoaderLogMessageTypeFlags message_type,
const XrLoaderLogMessengerCallbackData* callback_data) = 0;
----
Some utility methods that the `LoaderLogRecorder` base class supplies allow
controls over whether or not log messages are actually recorded.
Upon creation, a `LoaderLogRecorder` is set to actively record all messages
that contain the appropriate flags.
However, if we want to pause recording to one or more of the
`LoaderLogRecorders` at some point and then resume recording again later,
the following utilities can be used:
[source,c++]
----
virtual void Pause();
virtual void Resume();
bool IsPaused();
----
.LoaderLogRecorder Derived classes
Currently, there are two private classes derived from `LoaderLogRecorder`,
providing three basic behaviors:
* `OstreamLoaderLogRecorder`
** Outputs to `std::cerr` when created with `MakeStdErrLoaderLogRecorder()`
** Outputs to `std::cout` when created with `MakeStdOutLoaderLogRecorder()`
* `DebugUtilsLogRecorder`
** Created by `MakeDebugUtilsLoaderLogRecorder()`
The recorder created by `MakeStdErrLoaderLogRecorder()` handles recording
all error messages that occur in the loader out to `std::cerr`.
This logger is always enabled and is intended to always provide error
messages for easier issue diagnosis.
The recorder created by `MakeStdOutLoaderLogRecorder()` records messages out
to `std::cout`.
This logger is enabled when the <<loader-debugging, XR_LOADER_DEBUG>>
environment variable is defined.
The recorder created by `MakeDebugUtilsLoaderLogRecorder()` triggers an
`XR_EXT_debug_utils` callback every time a log message occurs.
Two steps are required before the loader enables this class:
1. The `XR_EXT_debug_utils` must: be enabled during fname:xrCreateInstance
call.
2. The application must: create a `XrDebugUtilsMessengerEXT` by
* Supplying an sname:XrDebugUtilsMessengerCreateInfoEXT structure to the
sname:XrInstanceCreateInfo::pname:next chain during
fname:xrCreateInstance, or
* Calling fname:xrCreateDebugUtilsMessengerEXT.
[[automatically-generated-code]]
=== Automatically Generated Code ===
In order to allow the OpenXR loader to be as flexible as possible, we
generate a large portion of the code automatically using the xr.xml registry
file.
The generation process is triggered during the build.
This is done inside the `CMakelists.txt` files in both the `src` and
`src/loader` folders, using the macro `run_xml_generate_dependency`.
This macro (defined in the `src/CMakelists.txt` file, triggers python and
generates the source.
The generation scripts are based on the functionality originally defined in
the of the `specification/scripts` folder, but here they've been extended to
generate loader source code.
The loader automatic code generation scripts are found in the `src/scripts`
folder.
The main script of interest for OpenXR loader code generation is
`automatic_source_generation.py` which generates 4 files during the build
process:
.Automatically Generated Loader Files
[width="70%",options="header",cols="50,50"]
|====
| Location | Filename
.2+| <build>/src folder
l| xr_generated_dispatch_table.h
l| xr_generated_dispatch_table.c
.2+| <build>/src/loader folder
l| xr_loader_generated.hpp
l| xr_loader_generated.cpp
|====
==== xr_generated_dispatch_table.h ====
This C-style header contains the definition of the
sname:XrGeneratedDispatchTable structure.
This structure can be used to store function pointers for any OpenXR
commands defined in the xr.xml at the time the loader was built.
It includes slots for both core and extension function pointers.
Currently, the loader uses this structure as well as the provided API
Layers.
A partial listing from the generated table follows:
[[XrGeneratedDispatchTable]]
[source,c++]
----
// Generated dispatch table
struct XrGeneratedDispatchTable {
// ---- Core 1.0 commands
PFN_xrGetInstanceProcAddr GetInstanceProcAddr;
PFN_xrEnumerateApiLayerProperties EnumerateApiLayerProperties;
PFN_xrEnumerateInstanceExtensionProperties EnumerateInstanceExtensionProperties;
PFN_xrCreateInstance CreateInstance;
PFN_xrDestroyInstance DestroyInstance;
...
};
----
You'll notice that the `xr` prefix was dropped on the name of the elements
within the structure to simplify naming as well as avoid any potential
compilation conflicts.
The `xr_generated_dispatch_table.h` header also includes a utility function
that can be used to populate a dispatch table once it has been created:
[[GeneratedXrPopulateDispatchTable,GeneratedXrPopulateDispatchTable]]
[source,c++]
----
void GeneratedXrPopulateDispatchTable(
struct XrGeneratedDispatchTable *table,
XrInstance instance,
PFN_xrGetInstanceProcAddr get_inst_proc_addr);
----
* pname:table is a pointer to the slink:XrGeneratedDispatchTable to
populate.
* pname:instance is the instance required by pname:get_inst_proc_addr.
*NOTE*: This may have a value of `XR_NULL_HANDLE`, but many of the
commands may be `NULL` if this is used.
* pname:get_inst_proc_addr is a pointer to the `xrGetInstanceProcAddr`
command to use to populate the table.
If you're calling into the OpenXR loader, this would be the standard
`xrGetInstanceProcAddr` call.
However, if you were calling this from an API layer, you would want to
use the next level's (API layer or runtime) implementation of
`xrGetInstanceProcAddr`.
==== xr_generated_dispatch_table.c ====
This file is paired with the above `xr_generated_dispatch_table.h` header
and only implements the flink:GeneratedXrPopulateDispatchTable function used
to populate the elements of a dispatch table.
==== xr_loader_generated.hpp ====
`xr_loader_generated.hpp` contains prototypes for all the manually defined
instance command trampoline and terminator functions.
This is done so that they can be referenced in the `xr_loader_generated.cpp`
source file which is used for `xrGetInstanceProcAddr` as well as setting up
the loader dispatch table.
==== xr_loader_generated.cpp ====
The `xr_loader_generated.cpp` source file contains the implementation of all
generated OpenXR trampoline functions.
[[manually-implemented-code]]
=== Manually Implemented Code ===
Some OpenXR command terminator and trampoline functions need to be manually
implemented in the loader.
.Manually Implemented OpenXR Commands
[width="90%",options="header",cols="<.^,^.^,<.^"]
|====
| Command | Terminator/Trampoline | Reason
| xrEnumerateApiLayerProperties
| Both (although terminator should never get called)
| Loader needs to find and parse the various API layer manifest files.
| xrEnumerateInstanceExtensionProperties
| Both
| Loader needs to find and parse the various API layer manifest files.
Also needs to call into runtime and query extensions supported by it.
| xrCreateInstance
| Both
| Loader needs to do all API layer and runtime discovery and processing as
well as storing the results. The storage is done inside a
`LoaderInstance` class object, which is created during this call.
| xrDestroyInstance
| Both
| Loader needs to call down to all API layers destroying the instance, and
then clean up its internal storage (i.e. the `LoaderInstance` class
that was created earlier).
| xrCreateApiLayerInstance
| Terminator
| Loader uses this to capture the
<<api-layer-create-instance-process,`xrCreateApiLayerInstance`>> chain used
to create API layer instances. This terminator will then re-direct the
chain back to the standard `xrCreateInstance` path.
|====
[[functional-flow]]
=== Functional Flow ===
The loader supports a single XrInstance at a time in order to avoid tracking
handle values and their relationship to the `LoaderInstance`.
Every XR function call is assumed to be for the single XrInstance that has
been created.
This enables the loader to work with future extensions and handle types
without change.
[[platform-specific-behavior]]
=== Platform-Specific Behavior ===
The OpenXR loader design is intended to be flexible on supported on a
variety of platforms.
However, the loader on certain platforms will require behavior not necessary
in other environments.
This section describes the common platform-specific behavior expected in the
OpenXR loader.
[[library-handling]]
==== Library Handling ====
The loader works with libraries and runtimes which are exposed as either a
static or dynamic external library files.
Each operating system provides their own utilities for interfacing with
these files, which the loader abstracts.
Most loader platform code can be found in the following source file:
[source,c++]
----
src/loader/loader_platform.hpp
----
The OpenXR loader uses a general handle define for all platform library
functions.
This handle is identified as `LoaderPlatformLibraryHandle` and is used to
interact with all the platform-specific library functions.
.Opening A Platform-Specific Library File
To open a platform-specific library file, and therefore retrieve the
platform-specific `LoaderPlatformLibraryHandle` relative to that file, the
loader calls the fname:LoaderPlatformLibraryOpen function which has the
following prototype:
[[LoaderPlatformLibraryOpen]]
[source,c++]
----
LoaderPlatformLibraryHandle LoaderPlatformLibraryOpen(
const std::string &path);
----
* pname:path must: be a constant string containing the absolute path to
the library file that needs to be opened.
If the function succeeds, the returned value will be non-NULL.
If a failure occurs during this call, the returned value will be NULL.
In the case of failure, the loader can call the
fname:LoaderPlatformLibraryOpenError function:
[[LoaderPlatformLibraryOpenError]]
[source,c++]
----
const char *LoaderPlatformLibraryOpenError(
const std::string &path);
----
* pname:path must: be a constant string containing the absolute path to
the library file that the loader previously attempted to load using
flink:LoaderPlatformLibraryOpen.
The returned C-style character string contains any available
platform-specific error code that may have occurred.
.Closing A Platform-Specific Library File
When the loader is done using the platform library, it calls
fname:LoaderPlatformLibraryClose to release it.
[[LoaderPlatformLibraryClose]]
[source,c++]
----
void LoaderPlatformLibraryClose(
LoaderPlatformLibraryHandle library)
----
* pname:library must: be valid `LoaderPlatformLibraryHandle` opened using
flink:LoaderPlatformLibraryOpen
.Querying Content In a Platform-Specific Library File
Once a library is opened, the loader will query for the important functions
exported by a library using the fname:LoaderPlatformLibraryGetProcAddr
function:
[[LoaderPlatformLibraryGetProcAddr]]
[source,c++]
----
void *LoaderPlatformLibraryGetProcAddr
LoaderPlatformLibraryHandle library,
const std::string &name)
----
* pname:library must: be valid `LoaderPlatformLibraryHandle` opened using
flink:LoaderPlatformLibraryOpen, but not yet closed using
flink:LoaderPlatformLibraryClose.
* pname:name must: contain the name of the library supplied function who's
function pointer is desired.
If the function succeeds, the returned value will be a valid function
pointer address.
If the function fails, it will return NULL.
A NULL return value could imply that the function simply isn't exported by
the library, or that an error occurred during the platform call.
To determine what might have happened, the loader will call the
fname:LoaderPlatformLibraryGetProcAddrError function:
[[LoaderPlatformLibraryGetProcAddrError]]
[source,c++]
----
const char *LoaderPlatformLibraryGetProcAddrError(
const std::string &path);
----
* pname:path must: be a constant string containing the name of the
entry-point that was attempted to be queried during the previous
flink:LoaderPlatformLibraryGetProcAddr call.
The returned C-style character string contains any available
platform-specific error code that may have occurred.
[[environment-variable-usage]]
==== Environment Variable Usage ====
Several environment variables are used in the OpenXR loader, especially on
the Desktop (Windows/Linux).
However, accessing environment variables is different based on each
operating system, so we have added global interface functions to use for
accessing environment variables.
These are defined in `src/common/platform_utils.hpp`
**NOTE:** This is outside of the loader source to allow other items in the
source folder to use these utilities.
.Reading a Standard Environment Variable
To read an environment variable, the loader calls fname:PlatformUtilsGetEnv:
[[PlatformUtilsGetEnv]]
[source,c++]
----
std::string PlatformUtilsGetEnv(
const char *name);
----
* pname:name must: be a non-NULL NULL-terminated C-style string indicating
the name of the environment variable to get the value of.
If the environment variable identified by pname:name exists on the system,
the C-style NULL-terminated string will be returned.
If the environment variable can not be found, an empty string is returned.
If you want to distinguish between an empty value and a variable not set at
all, if the underlying platform distinguishes these cases, use
fname:PlatformUtilsGetEnvSet:
[[PlatformUtilsGetEnvSet]]
[source,c++]
----
bool PlatformUtilsGetEnvSet(
const char *name);
----
It returns true if the environment variable is set.
.Reading a Secure Environment Variable
Access to certain environment variables needs to be done in a way that
maintains operational security of the program.
To read a secure environment variable, the loader calls
fname:PlatformUtilsGetSecureEnv:
[[PlatformUtilsGetSecureEnv]]
[source,c++]
----
char *PlatformUtilsGetSecureEnv(
const char *name);
----
* pname:name must: be a non-NULL NULL-terminated C-style string indicating
the name of the environment variable to get the value of.
If the platform supports secure environment variable reading, the
appropriate method will be used.
Otherwise, it will fall back to the standard flink:PlatformUtilsGetEnv call.
[[active-runtime-file-management]]
==== Active Runtime File Management ====
.Querying the Active Runtime File name
Since the runtime file name can vary based on the supporting system, the
command fname:PlatformGetGlobalRuntimeFileName provides a quick mechanism
for querying the name of the file.
[NOTE]
.Note
****
Not all platforms implement this call.
****
[[PlatformGetGlobalRuntimeFileName]]
[source,c++]
----
bool PlatformGetGlobalRuntimeFileName(
uint16_t major_version,
std::string &file_name);
----
* pname:major_version is the major API version for the OpenXR you are
querying the active runtime file name for.
* pname:file_name is the returned name of the runtime file.
This is only valid if the command returns `true`.
|