blob: 1218a2c971fe46521517b25533d54385568d04ca [file] [edit]
/*
* 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;
}