| /* | |
| * Copyright (C) 2026 Igalia S.L. All rights reserved. | |
| * | |
| * Redistribution and use in source and binary forms, with or without | |
| * modification, are permitted provided that the following conditions | |
| * are met: | |
| * 1. Redistributions of source code must retain the above copyright | |
| * notice, this list of conditions and the following disclaimer. | |
| * 2. Redistributions in binary form must reproduce the above copyright | |
| * notice, this list of conditions and the following disclaimer in the | |
| * documentation and/or other materials provided with the distribution. | |
| * | |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' | |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, | |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS | |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | |
| * THE POSSIBILITY OF SUCH DAMAGE. | |
| */ | |
| #include <cstring> | |
| #include <openxr/openxr.h> | |
| #include <openxr/openxr_loader_negotiation.h> | |
| #include <string_view> | |
| namespace { | |
| // WebKit only checks against XR_NULL_HANDLE | |
| int g_mockInstanceStorage = 0; | |
| XrInstance mockInstanceHandle() { return reinterpret_cast<XrInstance>(&g_mockInstanceStorage); } | |
| constexpr XrSystemId kMockSystemId = 1; | |
| void (*g_eventCallback)(const char* event, void* userData) = nullptr; | |
| void* g_eventCallbackUserData = nullptr; | |
| void notifyEvent(const char* event) | |
| { | |
| if (g_eventCallback) | |
| g_eventCallback(event, g_eventCallbackUserData); | |
| } | |
| template<typename T> | |
| XrResult enumerate(const T* values, uint32_t count, uint32_t capacityInput, uint32_t* countOutput, T* buffer) | |
| { | |
| if (countOutput) | |
| *countOutput = count; | |
| if (!capacityInput) | |
| return XR_SUCCESS; | |
| if (capacityInput < count) | |
| return XR_ERROR_SIZE_INSUFFICIENT; | |
| for (uint32_t i = 0; i < count; ++i) | |
| buffer[i] = values[i]; | |
| return XR_SUCCESS; | |
| } | |
| XRAPI_ATTR XrResult XRAPI_CALL mock_xrEnumerateInstanceExtensionProperties(const char*, uint32_t capacityInput, uint32_t* propertyCountOutput, XrExtensionProperties* properties) | |
| { | |
| static const char* const kExtensions[] = { "XR_KHR_opengl_es_enable" }; | |
| const uint32_t count = 1; | |
| if (propertyCountOutput) | |
| *propertyCountOutput = count; | |
| if (!capacityInput) | |
| return XR_SUCCESS; | |
| if (capacityInput < count) | |
| return XR_ERROR_SIZE_INSUFFICIENT; | |
| for (uint32_t i = 0; i < count; ++i) { | |
| properties[i].type = XR_TYPE_EXTENSION_PROPERTIES; | |
| properties[i].next = nullptr; | |
| std::strncpy(properties[i].extensionName, kExtensions[i], XR_MAX_EXTENSION_NAME_SIZE - 1); | |
| properties[i].extensionName[XR_MAX_EXTENSION_NAME_SIZE - 1] = '\0'; | |
| properties[i].extensionVersion = 1; | |
| } | |
| return XR_SUCCESS; | |
| } | |
| XRAPI_ATTR XrResult XRAPI_CALL mock_xrCreateInstance(const XrInstanceCreateInfo*, XrInstance* instance) | |
| { | |
| if (!instance) | |
| return XR_ERROR_VALIDATION_FAILURE; | |
| *instance = mockInstanceHandle(); | |
| notifyEvent("instance-created"); | |
| return XR_SUCCESS; | |
| } | |
| XRAPI_ATTR XrResult XRAPI_CALL mock_xrDestroyInstance(XrInstance) | |
| { | |
| notifyEvent("instance-destroyed"); | |
| return XR_SUCCESS; | |
| } | |
| XRAPI_ATTR XrResult XRAPI_CALL mock_xrGetSystem(XrInstance, const XrSystemGetInfo*, XrSystemId* systemId) | |
| { | |
| if (!systemId) | |
| return XR_ERROR_VALIDATION_FAILURE; | |
| *systemId = kMockSystemId; | |
| return XR_SUCCESS; | |
| } | |
| XRAPI_ATTR XrResult XRAPI_CALL mock_xrGetSystemProperties(XrInstance, XrSystemId, XrSystemProperties* properties) | |
| { | |
| if (!properties) | |
| return XR_ERROR_VALIDATION_FAILURE; | |
| properties->vendorId = 0xCAFE; | |
| std::strncpy(properties->systemName, "WebKit Mock OpenXR Runtime", XR_MAX_SYSTEM_NAME_SIZE - 1); | |
| properties->systemName[XR_MAX_SYSTEM_NAME_SIZE - 1] = '\0'; | |
| properties->trackingProperties.orientationTracking = XR_TRUE; | |
| properties->trackingProperties.positionTracking = XR_TRUE; | |
| properties->graphicsProperties.maxLayerCount = 1; | |
| properties->graphicsProperties.maxSwapchainImageWidth = 4096; | |
| properties->graphicsProperties.maxSwapchainImageHeight = 4096; | |
| // Leave properties->next untouched: WebKit chains XrSystemHandTrackingPropertiesEXT | |
| // and pre-sets supportsHandTracking = XR_FALSE. | |
| return XR_SUCCESS; | |
| } | |
| XRAPI_ATTR XrResult XRAPI_CALL mock_xrEnumerateViewConfigurations(XrInstance, XrSystemId, uint32_t capacityInput, uint32_t* countOutput, XrViewConfigurationType* buffer) | |
| { | |
| static const XrViewConfigurationType configs[] = { XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO }; | |
| return enumerate(configs, 1, capacityInput, countOutput, buffer); | |
| } | |
| XRAPI_ATTR XrResult XRAPI_CALL mock_xrEnumerateViewConfigurationViews(XrInstance, XrSystemId, XrViewConfigurationType, uint32_t capacityInput, uint32_t* countOutput, XrViewConfigurationView* buffer) | |
| { | |
| if (countOutput) | |
| *countOutput = 2; | |
| if (!capacityInput) | |
| return XR_SUCCESS; | |
| if (capacityInput < 2) | |
| return XR_ERROR_SIZE_INSUFFICIENT; | |
| for (uint32_t i = 0; i < 2; ++i) { | |
| buffer[i].recommendedImageRectWidth = 1024; | |
| buffer[i].maxImageRectWidth = 2048; | |
| buffer[i].recommendedImageRectHeight = 1024; | |
| buffer[i].maxImageRectHeight = 2048; | |
| buffer[i].recommendedSwapchainSampleCount = 1; | |
| buffer[i].maxSwapchainSampleCount = 1; | |
| } | |
| return XR_SUCCESS; | |
| } | |
| XRAPI_ATTR XrResult XRAPI_CALL mock_xrEnumerateEnvironmentBlendModes(XrInstance, XrSystemId, XrViewConfigurationType, uint32_t capacityInput, uint32_t* countOutput, XrEnvironmentBlendMode* buffer) | |
| { | |
| static const XrEnvironmentBlendMode modes[] = { XR_ENVIRONMENT_BLEND_MODE_OPAQUE }; | |
| return enumerate(modes, 1, capacityInput, countOutput, buffer); | |
| } | |
| XRAPI_ATTR XrResult XRAPI_CALL mock_xrGetOpenGLESGraphicsRequirementsKHR(XrInstance, XrSystemId, void*) | |
| { | |
| notifyEvent("GetOpenGLESGraphicsRequirementsKHR"); | |
| return XR_SUCCESS; | |
| } | |
| XRAPI_ATTR XrResult XRAPI_CALL mock_xrGetInstanceProcAddr(XrInstance, const char* name, PFN_xrVoidFunction* function) | |
| { | |
| if (!name || !function) | |
| return XR_ERROR_VALIDATION_FAILURE; | |
| std::string_view requested(name); | |
| auto bind = [&](PFN_xrVoidFunction fn) { | |
| *function = fn; | |
| return XR_SUCCESS; | |
| }; | |
| if (requested == "xrGetInstanceProcAddr") | |
| return bind(reinterpret_cast<PFN_xrVoidFunction>(mock_xrGetInstanceProcAddr)); | |
| if (requested == "xrEnumerateInstanceExtensionProperties") | |
| return bind(reinterpret_cast<PFN_xrVoidFunction>(mock_xrEnumerateInstanceExtensionProperties)); | |
| if (requested == "xrCreateInstance") | |
| return bind(reinterpret_cast<PFN_xrVoidFunction>(mock_xrCreateInstance)); | |
| if (requested == "xrDestroyInstance") | |
| return bind(reinterpret_cast<PFN_xrVoidFunction>(mock_xrDestroyInstance)); | |
| if (requested == "xrGetSystem") | |
| return bind(reinterpret_cast<PFN_xrVoidFunction>(mock_xrGetSystem)); | |
| if (requested == "xrGetSystemProperties") | |
| return bind(reinterpret_cast<PFN_xrVoidFunction>(mock_xrGetSystemProperties)); | |
| if (requested == "xrEnumerateViewConfigurations") | |
| return bind(reinterpret_cast<PFN_xrVoidFunction>(mock_xrEnumerateViewConfigurations)); | |
| if (requested == "xrEnumerateViewConfigurationViews") | |
| return bind(reinterpret_cast<PFN_xrVoidFunction>(mock_xrEnumerateViewConfigurationViews)); | |
| if (requested == "xrEnumerateEnvironmentBlendModes") | |
| return bind(reinterpret_cast<PFN_xrVoidFunction>(mock_xrEnumerateEnvironmentBlendModes)); | |
| if (requested == "xrGetOpenGLESGraphicsRequirementsKHR") | |
| return bind(reinterpret_cast<PFN_xrVoidFunction>(mock_xrGetOpenGLESGraphicsRequirementsKHR)); | |
| // Everything else (session, frame loop, spaces, input, ...) is unimplemented. | |
| *function = nullptr; | |
| return XR_ERROR_FUNCTION_UNSUPPORTED; | |
| } | |
| } // namespace | |
| #define MOCK_EXPORT extern "C" __attribute__((visibility("default"))) | |
| // Register a listener invoked synchronously on the calling thread. It is called back with | |
| // the event ("instance-created", "instance-destroyed"...) and the set user data. | |
| MOCK_EXPORT void mock_openxr_set_event_callback(void (*callback)(const char* event, void* userData), void* userData) | |
| { | |
| g_eventCallback = callback; | |
| g_eventCallbackUserData = userData; | |
| } | |
| MOCK_EXPORT XRAPI_ATTR XrResult XRAPI_CALL xrNegotiateLoaderRuntimeInterface(const XrNegotiateLoaderInfo* loaderInfo, XrNegotiateRuntimeRequest* runtimeRequest) | |
| { | |
| if (!loaderInfo || !runtimeRequest) | |
| return XR_ERROR_INITIALIZATION_FAILED; | |
| if (loaderInfo->structType != XR_LOADER_INTERFACE_STRUCT_LOADER_INFO | |
| || runtimeRequest->structType != XR_LOADER_INTERFACE_STRUCT_RUNTIME_REQUEST) | |
| return XR_ERROR_INITIALIZATION_FAILED; | |
| if (XR_CURRENT_LOADER_RUNTIME_VERSION < loaderInfo->minInterfaceVersion | |
| || XR_CURRENT_LOADER_RUNTIME_VERSION > loaderInfo->maxInterfaceVersion) | |
| return XR_ERROR_INITIALIZATION_FAILED; | |
| runtimeRequest->runtimeInterfaceVersion = XR_CURRENT_LOADER_RUNTIME_VERSION; | |
| runtimeRequest->runtimeApiVersion = XR_CURRENT_API_VERSION; | |
| runtimeRequest->getInstanceProcAddr = mock_xrGetInstanceProcAddr; | |
| return XR_SUCCESS; | |
| } |