mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Add missing includes on Windows. Include GLX only on Linux. Move openxr_platform.h include to OpenXrContext.h. To make this possible, certain names from GLX/X11 need to be undefined in order to be now includable in OpenXrInput.h. Add Overte e.V. copyright. Co-authored-by: Lubosz Sarnecki <lubosz@gmail.com>
558 lines
19 KiB
C++
558 lines
19 KiB
C++
//
|
|
// Overte OpenXR Plugin
|
|
//
|
|
// Copyright 2024 Lubosz Sarnecki
|
|
// Copyright 2024 Overte e.V.
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
#include "OpenXrDisplayPlugin.h"
|
|
#include <qloggingcategory.h>
|
|
|
|
#include "ViewFrustum.h"
|
|
|
|
#include <chrono>
|
|
#include <glm/gtx/string_cast.hpp>
|
|
#include <glm/gtx/transform.hpp>
|
|
#include <thread>
|
|
|
|
#if defined(Q_OS_WIN)
|
|
#undef near
|
|
#undef far
|
|
#endif
|
|
|
|
Q_DECLARE_LOGGING_CATEGORY(xr_display_cat)
|
|
Q_LOGGING_CATEGORY(xr_display_cat, "openxr.display")
|
|
|
|
constexpr GLint XR_PREFERRED_COLOR_FORMAT = GL_SRGB8_ALPHA8;
|
|
|
|
OpenXrDisplayPlugin::OpenXrDisplayPlugin(std::shared_ptr<OpenXrContext> c) {
|
|
_context = c;
|
|
_presentOnlyOnce = true;
|
|
}
|
|
|
|
bool OpenXrDisplayPlugin::isSupported() const {
|
|
return _context->_isSupported;
|
|
}
|
|
|
|
// Slightly differs from glm::ortho
|
|
inline static glm::mat4 fovToProjection(const XrFovf fov, const float near, const float far) {
|
|
const float left = tanf(fov.angleLeft);
|
|
const float right = tanf(fov.angleRight);
|
|
const float down = tanf(fov.angleDown);
|
|
const float up = tanf(fov.angleUp);
|
|
|
|
const float width = right - left;
|
|
const float height = up - down;
|
|
|
|
const float m11 = 2 / width;
|
|
const float m22 = 2 / height;
|
|
const float m33 = -(far + near) / (far - near);
|
|
|
|
const float m31 = (right + left) / width;
|
|
const float m32 = (up + down) / height;
|
|
const float m43 = -(far * (near + near)) / (far - near);
|
|
|
|
// clang-format off
|
|
const float mat[16] = {
|
|
m11, 0 , 0 , 0,
|
|
0 , m22, 0 , 0,
|
|
m31, m32, m33, -1,
|
|
0 , 0 , m43, 0,
|
|
};
|
|
// clang-format on
|
|
|
|
return glm::make_mat4(mat);
|
|
}
|
|
|
|
glm::mat4 OpenXrDisplayPlugin::getEyeProjection(Eye eye, const glm::mat4& baseProjection) const {
|
|
if (!_viewsInitialized) {
|
|
return baseProjection;
|
|
}
|
|
|
|
ViewFrustum frustum;
|
|
frustum.setProjection(baseProjection);
|
|
return fovToProjection(_views[(eye == Left) ? 0 : 1].fov, frustum.getNearClip(), frustum.getFarClip());
|
|
}
|
|
|
|
// TODO: This apparently wasn't right in the OpenVR plugin, but this is what it basically did.
|
|
glm::mat4 OpenXrDisplayPlugin::getCullingProjection(const glm::mat4& baseProjection) const {
|
|
return getEyeProjection(Left, baseProjection);
|
|
}
|
|
|
|
// TODO: This should not be explicilty known by the application.
|
|
// Let's just render as fast as we can and OpenXR will dictate the pace.
|
|
float OpenXrDisplayPlugin::getTargetFrameRate() const {
|
|
return std::numeric_limits<float>::max();
|
|
}
|
|
|
|
bool OpenXrDisplayPlugin::initViews() {
|
|
XrInstance instance = _context->_instance;
|
|
XrSystemId systemId = _context->_systemId;
|
|
|
|
XrResult result = xrEnumerateViewConfigurationViews(instance, systemId, XR_VIEW_CONFIG_TYPE, 0, &_viewCount, nullptr);
|
|
if (!xrCheck(instance, result, "Failed to get view configuration view count!")) {
|
|
qCCritical(xr_display_cat, "Failed to get view configuration view count!");
|
|
return false;
|
|
}
|
|
|
|
assert(_viewCount != 0);
|
|
|
|
for (uint32_t i = 0; i < _viewCount; i++) {
|
|
XrView view = { .type = XR_TYPE_VIEW };
|
|
_views.push_back(view);
|
|
|
|
XrViewConfigurationView viewConfig = { .type = XR_TYPE_VIEW_CONFIGURATION_VIEW };
|
|
_viewConfigs.push_back(viewConfig);
|
|
}
|
|
|
|
_swapChains.resize(_viewCount);
|
|
_swapChainLengths.resize(_viewCount);
|
|
_swapChainIndices.resize(_viewCount);
|
|
_images.resize(_viewCount);
|
|
|
|
result = xrEnumerateViewConfigurationViews(instance, systemId, XR_VIEW_CONFIG_TYPE, _viewCount, &_viewCount,
|
|
_viewConfigs.data());
|
|
if (!xrCheck(instance, result, "Failed to enumerate view configuration views!")) {
|
|
qCCritical(xr_display_cat, "Failed to enumerate view configuration views!");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#define ENUM_TO_STR(r) \
|
|
case r: \
|
|
return #r
|
|
|
|
static std::string glFormatStr(GLenum source) {
|
|
switch (source) {
|
|
ENUM_TO_STR(GL_RGBA16);
|
|
ENUM_TO_STR(GL_RGBA16F);
|
|
ENUM_TO_STR(GL_SRGB8_ALPHA8);
|
|
default: {
|
|
// TODO: Enable C++20 for std::format
|
|
std::ostringstream ss;
|
|
ss << "0x" << std::hex << source;
|
|
return ss.str();
|
|
}
|
|
}
|
|
}
|
|
|
|
static int64_t chooseSwapChainFormat(XrInstance instance, XrSession session, int64_t preferred) {
|
|
uint32_t formatCount;
|
|
XrResult result = xrEnumerateSwapchainFormats(session, 0, &formatCount, nullptr);
|
|
if (!xrCheck(instance, result, "Failed to get number of supported swapchain formats"))
|
|
return -1;
|
|
|
|
qCInfo(xr_display_cat, "Runtime supports %d swapchain formats", formatCount);
|
|
std::vector<int64_t> formats(formatCount);
|
|
|
|
result = xrEnumerateSwapchainFormats(session, formatCount, &formatCount, formats.data());
|
|
if (!xrCheck(instance, result, "Failed to enumerate swapchain formats"))
|
|
return -1;
|
|
|
|
int64_t chosen = formats[0];
|
|
|
|
for (uint32_t i = 0; i < formatCount; i++) {
|
|
qCInfo(xr_display_cat, "Supported GL format: %s", glFormatStr(formats[i]).c_str());
|
|
if (formats[i] == preferred) {
|
|
chosen = formats[i];
|
|
qCInfo(xr_display_cat, "Using preferred swapchain format %s", glFormatStr(chosen).c_str());
|
|
break;
|
|
}
|
|
}
|
|
if (chosen != preferred) {
|
|
qCWarning(xr_display_cat, "Falling back to non preferred swapchain format %s", glFormatStr(chosen).c_str());
|
|
}
|
|
|
|
return chosen;
|
|
}
|
|
|
|
bool OpenXrDisplayPlugin::initSwapChains() {
|
|
XrInstance instance = _context->_instance;
|
|
XrSession session = _context->_session;
|
|
|
|
int64_t format = chooseSwapChainFormat(instance, session, XR_PREFERRED_COLOR_FORMAT);
|
|
|
|
for (uint32_t i = 0; i < _viewCount; i++) {
|
|
_images[i].clear();
|
|
|
|
XrSwapchainCreateInfo info = {
|
|
.type = XR_TYPE_SWAPCHAIN_CREATE_INFO,
|
|
.createFlags = 0,
|
|
.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT,
|
|
.format = format,
|
|
.sampleCount = _viewConfigs[i].recommendedSwapchainSampleCount,
|
|
.width = _viewConfigs[i].recommendedImageRectWidth,
|
|
.height = _viewConfigs[i].recommendedImageRectHeight,
|
|
.faceCount = 1,
|
|
.arraySize = 1,
|
|
.mipCount = 1,
|
|
};
|
|
|
|
XrResult result = xrCreateSwapchain(session, &info, &_swapChains[i]);
|
|
if (!xrCheck(instance, result, "Failed to create swapchain!"))
|
|
return false;
|
|
|
|
result = xrEnumerateSwapchainImages(_swapChains[i], 0, &_swapChainLengths[i], nullptr);
|
|
if (!xrCheck(instance, result, "Failed to enumerate swapchains"))
|
|
return false;
|
|
|
|
for (uint32_t j = 0; j < _swapChainLengths[i]; j++) {
|
|
XrSwapchainImageOpenGLKHR image = { .type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR };
|
|
_images[i].push_back(image);
|
|
}
|
|
result = xrEnumerateSwapchainImages(_swapChains[i], _swapChainLengths[i], &_swapChainLengths[i],
|
|
(XrSwapchainImageBaseHeader*)_images[i].data());
|
|
if (!xrCheck(instance, result, "Failed to enumerate swapchain images"))
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool OpenXrDisplayPlugin::initLayers() {
|
|
for (uint32_t i = 0; i < _viewCount; i++) {
|
|
XrCompositionLayerProjectionView layer = {
|
|
.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW,
|
|
.subImage = {
|
|
.swapchain = _swapChains[i],
|
|
.imageRect = {
|
|
.offset = {
|
|
.x = 0,
|
|
.y = 0,
|
|
},
|
|
.extent = {
|
|
.width = (int32_t)_viewConfigs[i].recommendedImageRectWidth,
|
|
.height = (int32_t)_viewConfigs[i].recommendedImageRectHeight,
|
|
},
|
|
},
|
|
.imageArrayIndex = 0,
|
|
},
|
|
};
|
|
_projectionLayerViews.push_back(layer);
|
|
};
|
|
|
|
return true;
|
|
}
|
|
|
|
void OpenXrDisplayPlugin::init() {
|
|
Plugin::init();
|
|
|
|
if (!initViews()) {
|
|
qCCritical(xr_display_cat, "View init failed.");
|
|
return;
|
|
}
|
|
|
|
for (const XrViewConfigurationView& view : _viewConfigs) {
|
|
assert(view.recommendedImageRectWidth != 0);
|
|
qCDebug(xr_display_cat, "Swapchain dimensions: %dx%d", view.recommendedImageRectWidth, view.recommendedImageRectHeight);
|
|
// TODO: Don't render side-by-side but use multiview (texture arrays). This probably won't work with GL.
|
|
_renderTargetSize.x = view.recommendedImageRectWidth * 2;
|
|
_renderTargetSize.y = view.recommendedImageRectHeight;
|
|
}
|
|
|
|
emit deviceConnected(getName());
|
|
}
|
|
|
|
const QString OpenXrDisplayPlugin::getName() const {
|
|
return QString("OpenXR: %1").arg(_context->_systemName);
|
|
}
|
|
|
|
bool OpenXrDisplayPlugin::internalActivate() {
|
|
_context->reset();
|
|
return HmdDisplayPlugin::internalActivate();
|
|
}
|
|
|
|
void OpenXrDisplayPlugin::internalDeactivate() {
|
|
// We can get into a state where activate -> deactivate -> activate is called in a chain.
|
|
// We are probably gonna have a bad time then. At least check if the session is already running.
|
|
// This happens when the application decides to switch display plugins back and forth. This should
|
|
// probably be fixed there.
|
|
if (_context->_isSessionRunning) {
|
|
if (!_context->requestExitSession()) {
|
|
qCCritical(xr_display_cat, "Failed to request exit session");
|
|
} else {
|
|
// Poll events until runtime wants to quit
|
|
while (!_context->_shouldQuit) {
|
|
_context->pollEvents();
|
|
}
|
|
}
|
|
}
|
|
HmdDisplayPlugin::internalDeactivate();
|
|
}
|
|
|
|
void OpenXrDisplayPlugin::customizeContext() {
|
|
gl::initModuleGl();
|
|
HmdDisplayPlugin::customizeContext();
|
|
|
|
if (!_context->initPostGraphics()) {
|
|
qCCritical(xr_display_cat, "Post graphics init failed.");
|
|
return;
|
|
}
|
|
|
|
if (!initSwapChains()) {
|
|
qCCritical(xr_display_cat, "Swap chain init failed.");
|
|
return;
|
|
}
|
|
|
|
if (!initLayers()) {
|
|
qCCritical(xr_display_cat, "Layer init failed.");
|
|
return;
|
|
}
|
|
|
|
// Create swap chain images for _compositeFramebuffer
|
|
for (size_t i = 0; i < _swapChainLengths[0]; ++i) {
|
|
gpu::TexturePointer texture =
|
|
gpu::Texture::createRenderBuffer(gpu::Element::COLOR_SRGBA_32, _renderTargetSize.x, _renderTargetSize.y,
|
|
gpu::Texture::SINGLE_MIP, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT));
|
|
_compositeSwapChain.push_back(texture);
|
|
}
|
|
}
|
|
|
|
void OpenXrDisplayPlugin::uncustomizeContext() {
|
|
_compositeSwapChain.clear();
|
|
_projectionLayerViews.clear();
|
|
for (uint32_t i = 0; i < _viewCount; i++) {
|
|
_images[i].clear();
|
|
}
|
|
HmdDisplayPlugin::uncustomizeContext();
|
|
}
|
|
|
|
void OpenXrDisplayPlugin::resetSensors() {
|
|
}
|
|
|
|
bool OpenXrDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
|
|
_context->pollEvents();
|
|
|
|
if (_context->_shouldQuit) {
|
|
QMetaObject::invokeMethod(qApp, "quit");
|
|
return false;
|
|
}
|
|
|
|
if (!_context->_shouldRunFrameCycle) {
|
|
qCWarning(xr_display_cat, "beginFrameRender: Shoudln't run frame cycle. Skipping renderin frame %d", frameIndex);
|
|
return true;
|
|
}
|
|
|
|
// Wait for present thread
|
|
// Actually wait for xrEndFrame to happen.
|
|
bool haveFrameToSubmit = true;
|
|
{
|
|
std::unique_lock<std::mutex> lock(_haveFrameMutex);
|
|
haveFrameToSubmit = _haveFrameToSubmit;
|
|
}
|
|
|
|
while (haveFrameToSubmit) {
|
|
std::this_thread::sleep_for(std::chrono::microseconds(10));
|
|
{
|
|
std::unique_lock<std::mutex> lock(_haveFrameMutex);
|
|
haveFrameToSubmit = _haveFrameToSubmit;
|
|
}
|
|
}
|
|
|
|
_lastFrameState = { .type = XR_TYPE_FRAME_STATE };
|
|
XrResult result = xrWaitFrame(_context->_session, nullptr, &_lastFrameState);
|
|
|
|
if (!xrCheck(_context->_instance, result, "xrWaitFrame failed"))
|
|
return false;
|
|
|
|
if (!_context->beginFrame())
|
|
return false;
|
|
|
|
_context->_lastPredictedDisplayTime = _lastFrameState.predictedDisplayTime;
|
|
_context->_lastPredictedDisplayTimeInitialized = true;
|
|
|
|
std::vector<XrView> eye_views(_viewCount);
|
|
for (uint32_t i = 0; i < _viewCount; i++) {
|
|
eye_views[i].type = XR_TYPE_VIEW;
|
|
}
|
|
|
|
// TODO: Probably shouldn't call xrLocateViews twice. Use only view space views?
|
|
XrViewLocateInfo eyeViewLocateInfo = {
|
|
.type = XR_TYPE_VIEW_LOCATE_INFO,
|
|
.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
|
|
.displayTime = _lastFrameState.predictedDisplayTime,
|
|
.space = _context->_viewSpace,
|
|
};
|
|
|
|
XrViewState eyeViewState = { .type = XR_TYPE_VIEW_STATE };
|
|
|
|
result = xrLocateViews(_context->_session, &eyeViewLocateInfo, &eyeViewState, _viewCount, &_viewCount, eye_views.data());
|
|
if (!xrCheck(_context->_instance, result, "Could not locate views"))
|
|
return false;
|
|
|
|
for (uint32_t i = 0; i < 2; i++) {
|
|
vec3 eyePosition = xrVecToGlm(eye_views[i].pose.position);
|
|
quat eyeOrientation = xrQuatToGlm(eye_views[i].pose.orientation);
|
|
_eyeOffsets[i] = controller::Pose(eyePosition, eyeOrientation).getMatrix();
|
|
}
|
|
|
|
_lastViewState = { .type = XR_TYPE_VIEW_STATE };
|
|
|
|
XrViewLocateInfo viewLocateInfo = {
|
|
.type = XR_TYPE_VIEW_LOCATE_INFO,
|
|
.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
|
|
.displayTime = _lastFrameState.predictedDisplayTime,
|
|
.space = _context->_stageSpace,
|
|
};
|
|
|
|
result = xrLocateViews(_context->_session, &viewLocateInfo, &_lastViewState, _viewCount, &_viewCount, _views.data());
|
|
if (!xrCheck(_context->_instance, result, "Could not locate views"))
|
|
return false;
|
|
|
|
for (uint32_t i = 0; i < _viewCount; i++) {
|
|
_projectionLayerViews[i].pose = _views[i].pose;
|
|
_projectionLayerViews[i].fov = _views[i].fov;
|
|
}
|
|
|
|
_viewsInitialized = true;
|
|
|
|
XrSpaceLocation headLocation = {
|
|
.type = XR_TYPE_SPACE_LOCATION,
|
|
.pose = XR_INDENTITY_POSE,
|
|
};
|
|
xrLocateSpace(_context->_viewSpace, _context->_stageSpace, _lastFrameState.predictedDisplayTime, &headLocation);
|
|
|
|
glm::vec3 headPosition = xrVecToGlm(headLocation.pose.position);
|
|
glm::quat headOrientation = xrQuatToGlm(headLocation.pose.orientation);
|
|
_context->_lastHeadPose = controller::Pose(headPosition, headOrientation);
|
|
|
|
_currentRenderFrameInfo = FrameInfo();
|
|
_currentRenderFrameInfo.renderPose = _context->_lastHeadPose.getMatrix();
|
|
_currentRenderFrameInfo.presentPose = _currentRenderFrameInfo.renderPose;
|
|
_frameInfos[frameIndex] = _currentRenderFrameInfo;
|
|
|
|
return HmdDisplayPlugin::beginFrameRender(frameIndex);
|
|
}
|
|
|
|
void OpenXrDisplayPlugin::submitFrame(const gpu::FramePointer& newFrame) {
|
|
OpenGLDisplayPlugin::submitFrame(newFrame);
|
|
{
|
|
std::unique_lock<std::mutex> lock(_haveFrameMutex);
|
|
_haveFrameToSubmit = true;
|
|
}
|
|
}
|
|
|
|
void OpenXrDisplayPlugin::compositeLayers() {
|
|
if (!_context->_shouldRunFrameCycle) {
|
|
return;
|
|
}
|
|
|
|
if (_lastFrameState.shouldRender) {
|
|
_compositeFramebuffer->setRenderBuffer(0, _compositeSwapChain[_swapChainIndices[0]]);
|
|
HmdDisplayPlugin::compositeLayers();
|
|
}
|
|
}
|
|
|
|
void OpenXrDisplayPlugin::hmdPresent() {
|
|
if (!_context->_shouldRunFrameCycle) {
|
|
qCWarning(xr_display_cat, "hmdPresent: Shoudln't run frame cycle. Skipping renderin frame %d",
|
|
_currentFrame->frameIndex);
|
|
return;
|
|
}
|
|
|
|
if (_lastFrameState.shouldRender) {
|
|
// TODO: Use multiview swapchain
|
|
for (uint32_t i = 0; i < 2; i++) {
|
|
XrSwapchainImageAcquireInfo acquireInfo = { .type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO };
|
|
|
|
XrResult result = xrAcquireSwapchainImage(_swapChains[i], &acquireInfo, &_swapChainIndices[i]);
|
|
if (!xrCheck(_context->_instance, result, "failed to acquire swapchain image!"))
|
|
return;
|
|
|
|
XrSwapchainImageWaitInfo waitInfo = { .type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO, .timeout = 1000 };
|
|
result = xrWaitSwapchainImage(_swapChains[i], &waitInfo);
|
|
if (!xrCheck(_context->_instance, result, "failed to wait for swapchain image!"))
|
|
return;
|
|
}
|
|
|
|
GLuint glTexId = getGLBackend()->getTextureID(_compositeFramebuffer->getRenderBuffer(0));
|
|
|
|
glCopyImageSubData(glTexId, GL_TEXTURE_2D, 0, 0, 0, 0, _images[0][_swapChainIndices[0]].image, GL_TEXTURE_2D, 0, 0, 0,
|
|
0, _renderTargetSize.x / 2, _renderTargetSize.y, 1);
|
|
|
|
glCopyImageSubData(glTexId, GL_TEXTURE_2D, 0, _renderTargetSize.x / 2, 0, 0, _images[1][_swapChainIndices[1]].image,
|
|
GL_TEXTURE_2D, 0, 0, 0, 0, _renderTargetSize.x / 2, _renderTargetSize.y, 1);
|
|
|
|
for (uint32_t i = 0; i < 2; i++) {
|
|
XrSwapchainImageReleaseInfo releaseInfo = { .type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO };
|
|
XrResult result = xrReleaseSwapchainImage(_swapChains[i], &releaseInfo);
|
|
if (!xrCheck(_context->_instance, result, "failed to release swapchain image!")) {
|
|
assert(false);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
endFrame();
|
|
|
|
_presentRate.increment();
|
|
|
|
{
|
|
std::unique_lock<std::mutex> lock(_haveFrameMutex);
|
|
_haveFrameToSubmit = false;
|
|
}
|
|
}
|
|
|
|
bool OpenXrDisplayPlugin::endFrame() {
|
|
XrCompositionLayerProjection projectionLayer = {
|
|
.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION,
|
|
.layerFlags = 0,
|
|
.space = _context->_stageSpace,
|
|
.viewCount = _viewCount,
|
|
.views = _projectionLayerViews.data(),
|
|
};
|
|
|
|
std::vector<const XrCompositionLayerBaseHeader*> layers = {
|
|
(const XrCompositionLayerBaseHeader*)&projectionLayer,
|
|
};
|
|
|
|
XrFrameEndInfo info = {
|
|
.type = XR_TYPE_FRAME_END_INFO,
|
|
.displayTime = _lastFrameState.predictedDisplayTime,
|
|
.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE,
|
|
.layerCount = (uint32_t)layers.size(),
|
|
.layers = layers.data(),
|
|
};
|
|
|
|
if ((_lastViewState.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) == 0) {
|
|
qCWarning(xr_display_cat, "Not submitting layers because orientation is invalid.");
|
|
info.layerCount = 0;
|
|
}
|
|
|
|
if (!_lastFrameState.shouldRender) {
|
|
info.layerCount = 0;
|
|
}
|
|
|
|
XrResult result = xrEndFrame(_context->_session, &info);
|
|
if (!xrCheck(_context->_instance, result, "failed to end frame!")) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void OpenXrDisplayPlugin::postPreview() {
|
|
}
|
|
|
|
bool OpenXrDisplayPlugin::isHmdMounted() const {
|
|
return true;
|
|
}
|
|
|
|
void OpenXrDisplayPlugin::updatePresentPose() {
|
|
}
|
|
|
|
int OpenXrDisplayPlugin::getRequiredThreadCount() const {
|
|
return HmdDisplayPlugin::getRequiredThreadCount();
|
|
}
|
|
|
|
QRectF OpenXrDisplayPlugin::getPlayAreaRect() {
|
|
return QRectF(0, 0, 10, 10);
|
|
}
|
|
|
|
DisplayPlugin::StencilMaskMeshOperator OpenXrDisplayPlugin::getStencilMaskMeshOperator() {
|
|
return nullptr;
|
|
}
|