Files
UltraGrid/src/video_display/openxr_gl.cpp
Martin Pulec 226c17f02d replace all other AC-defined platform macro
Replaced all other autoconf-defined platform macros (HAVE_LINUX and
HAVE_MACOSX, WIN32 already done) with those ones defined by compiler.

Not yet remove the definitions from autoconf, in case someone will use
the old macros anyways. Remove in future.
2024-05-13 12:56:53 +02:00

1167 lines
45 KiB
C++

/**
* @file video_display/openxr_gl.cpp
* @author Martin Piatka <piatka@cesnet.cz>
*/
/*
* Copyright (c) 2010-2023 CESNET, z. s. p. o.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, is 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.
*
* 3. Neither the name of CESNET nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESSED 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 THE AUTHORS OR 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.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
# include "config_unix.h"
# include "config_win32.h"
#endif //HAVE_CONFIG_H
#include <chrono>
#include <thread>
#include <vector>
#include <condition_variable>
#include <queue>
#include <assert.h>
#ifdef __linux__
# include <X11/Xlib.h>
# include <GL/glew.h>
# include <GL/glx.h>
# define XR_USE_PLATFORM_XLIB
# define XR_USE_GRAPHICS_API_OPENGL
#elif defined _WIN32
# define XR_USE_PLATFORM_WIN32
# define XR_USE_GRAPHICS_API_OPENGL
#endif
#include <openxr/openxr.h>
#include <openxr/openxr_platform.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/quaternion.hpp>
#include "debug.h"
#include "host.h"
#include "keyboard_control.h" // K_*
#include "lib_common.h"
#include "messaging.h"
#include "module.h"
#include "video.h"
#include "video_display.h"
#include "video_display/splashscreen.h"
#include "opengl_utils.hpp"
#include "opengl_panorama.hpp"
#include "sdl_window.hpp"
#include "utils/profile_timer.hpp"
#define MAX_BUFFER_SIZE 3
class Openxr_session{
public:
Openxr_session(XrInstance instance,
XrSystemId systemId,
const void *xrGraphicsBindingOpenGL)
{
XrSessionCreateInfo session_create_info = {};
session_create_info.type = XR_TYPE_SESSION_CREATE_INFO;
session_create_info.next = xrGraphicsBindingOpenGL;
session_create_info.systemId = systemId;
XrResult result;
PFN_xrGetOpenGLGraphicsRequirementsKHR pfnGetOpenGLGraphicsRequirementsKHR = nullptr;
result = xrGetInstanceProcAddr(instance, "xrGetOpenGLGraphicsRequirementsKHR",
reinterpret_cast<PFN_xrVoidFunction*>(&pfnGetOpenGLGraphicsRequirementsKHR));
XrGraphicsRequirementsOpenGLKHR graphicsRequirements{};
graphicsRequirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_OPENGL_KHR;
result = pfnGetOpenGLGraphicsRequirementsKHR(instance, systemId, &graphicsRequirements);
result = xrCreateSession(instance, &session_create_info, &session);
if(!XR_SUCCEEDED(result)){
throw std::runtime_error("Failed to create OpenXR session!");
}
}
~Openxr_session(){
xrDestroySession(session);
}
XrSession get(){ return session; }
void begin(){
XrSessionBeginInfo session_begin_info;
session_begin_info.type = XR_TYPE_SESSION_BEGIN_INFO;
session_begin_info.next = NULL;
session_begin_info.primaryViewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO;
XrResult result = xrBeginSession(session, &session_begin_info);
if(!XR_SUCCEEDED(result)){
throw std::runtime_error("Failed to begin OpenXR session!");
}
}
private:
XrSession session;
};
class Openxr_instance{
public:
Openxr_instance(){
uint32_t properties_count = 0;
XrResult result = xrEnumerateInstanceExtensionProperties(nullptr,
properties_count,
&properties_count,
nullptr);
if(!XR_SUCCEEDED(result)){
log_msg(LOG_LEVEL_WARNING, "Failed to check XR_KHR_OPENGL availability!\n");
} else {
std::vector<XrExtensionProperties> props;
props.resize(properties_count);
for(auto& prop : props){
prop.type = XR_TYPE_EXTENSION_PROPERTIES;
prop.next = nullptr;
}
result = xrEnumerateInstanceExtensionProperties(nullptr,
properties_count,
&properties_count,
props.data());
bool found = false;
for(const auto& prop : props){
if(strcmp(prop.extensionName, XR_KHR_OPENGL_ENABLE_EXTENSION_NAME) == 0){
found = true;
break;
}
}
if(!found){
throw std::runtime_error("OpenXR runtime does not support OpenGL interop!");
}
}
const char* const enabledExtensions[] = {XR_KHR_OPENGL_ENABLE_EXTENSION_NAME};
XrInstanceCreateInfo instanceCreateInfo;
instanceCreateInfo.type = XR_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo.next = NULL;
instanceCreateInfo.createFlags = 0;
instanceCreateInfo.enabledExtensionCount = 1;
instanceCreateInfo.enabledExtensionNames = enabledExtensions;
instanceCreateInfo.enabledApiLayerCount = 0;
strcpy(instanceCreateInfo.applicationInfo.applicationName, "UltraGrid OpenXR gl display");
strcpy(instanceCreateInfo.applicationInfo.engineName, "");
instanceCreateInfo.applicationInfo.applicationVersion = 1;
instanceCreateInfo.applicationInfo.engineVersion = 0;
instanceCreateInfo.applicationInfo.apiVersion = XR_CURRENT_API_VERSION;
result = xrCreateInstance(&instanceCreateInfo, &instance);
if(!XR_SUCCEEDED(result)){
throw std::runtime_error("Failed to create OpenXR instance!");
}
XrInstanceProperties instanceProperties;
instanceProperties.type = XR_TYPE_INSTANCE_PROPERTIES;
instanceProperties.next = NULL;
result = xrGetInstanceProperties(instance, &instanceProperties);
if(XR_SUCCEEDED(result)){
printf("Runtime Name: %s\n", instanceProperties.runtimeName);
printf("Runtime Version: %d.%d.%d\n",
XR_VERSION_MAJOR(instanceProperties.runtimeVersion),
XR_VERSION_MINOR(instanceProperties.runtimeVersion),
XR_VERSION_PATCH(instanceProperties.runtimeVersion));
}
}
~Openxr_instance(){
xrDestroyInstance(instance);
}
Openxr_instance(const Openxr_instance&) = delete;
Openxr_instance(Openxr_instance&&) = delete;
Openxr_instance& operator=(const Openxr_instance&) = delete;
Openxr_instance& operator=(Openxr_instance&&) = delete;
XrInstance get() { return instance; }
private:
XrInstance instance;
};
class Openxr_swapchain{
public:
Openxr_swapchain() = default;
Openxr_swapchain(XrSession session, const XrSwapchainCreateInfo *info) :
session(session)
{
xrCreateSwapchain(session, info, &swapchain);
}
Openxr_swapchain(XrSession session,
int64_t swapchain_format,
uint32_t w,
uint32_t h) :
session(session)
{
XrSwapchainCreateInfo swapchain_create_info;
swapchain_create_info.type = XR_TYPE_SWAPCHAIN_CREATE_INFO;
swapchain_create_info.usageFlags = XR_SWAPCHAIN_USAGE_SAMPLED_BIT |
XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT;
swapchain_create_info.createFlags = 0;
swapchain_create_info.format = swapchain_format;
swapchain_create_info.sampleCount = 1;
swapchain_create_info.width = w;
swapchain_create_info.height = h;
swapchain_create_info.faceCount = 1;
swapchain_create_info.arraySize = 1;
swapchain_create_info.mipCount = 1;
swapchain_create_info.next = nullptr;
XrResult result = xrCreateSwapchain(session, &swapchain_create_info, &swapchain);
if(!XR_SUCCEEDED(result)){
throw std::runtime_error("Failed to create OpenXR swapchain!");
}
}
~Openxr_swapchain(){
if(swapchain != XR_NULL_HANDLE){
xrDestroySwapchain(swapchain);
}
}
Openxr_swapchain(const Openxr_swapchain&) = delete;
Openxr_swapchain(Openxr_swapchain&& o) noexcept : Openxr_swapchain() {
swap(o);
}
Openxr_swapchain& operator=(const Openxr_swapchain&) = delete;
Openxr_swapchain& operator=(Openxr_swapchain&& o) { swap(o); return *this; }
uint32_t get_length(){
uint32_t len = 0;
XrResult result = xrEnumerateSwapchainImages(swapchain, 0, &len, nullptr);
if(!XR_SUCCEEDED(result)){
throw std::runtime_error("Failed to enumerate swapchain images\n");
}
return len;
}
void swap(Openxr_swapchain& o) noexcept{
std::swap(swapchain, o.swapchain);
std::swap(session, o.session);
}
XrSwapchain get(){ return swapchain; }
private:
XrSwapchain swapchain = XR_NULL_HANDLE;
XrSession session = XR_NULL_HANDLE;
};
class Gl_interop_swapchain{
public:
Gl_interop_swapchain(XrSession session, const XrSwapchainCreateInfo *info) :
xr_swapchain(session, info)
{
init();
}
Gl_interop_swapchain(XrSession session,
int64_t swapchain_format,
uint32_t w,
uint32_t h) :
xr_swapchain(session, swapchain_format, w, h)
{
init();
}
GLuint get_texture(size_t idx){
return images[idx].image;
}
Framebuffer& get_framebuffer(size_t idx){
return framebuffers[idx];
}
XrSwapchain get() { return xr_swapchain.get(); }
Gl_interop_swapchain(const Gl_interop_swapchain&) = delete;
Gl_interop_swapchain(Gl_interop_swapchain&&) = default;
Gl_interop_swapchain& operator=(const Gl_interop_swapchain&) = delete;
Gl_interop_swapchain& operator=(Gl_interop_swapchain&&) = default;
private:
void init(){
uint32_t length = xr_swapchain.get_length();
images.resize(length);
framebuffers.resize(length);
for(auto& image : images){
image.type = XR_TYPE_SWAPCHAIN_IMAGE_OPENGL_KHR;
image.next = nullptr;
}
XrResult result = xrEnumerateSwapchainImages(xr_swapchain.get(),
length, &length,
(XrSwapchainImageBaseHeader *)(images.data()));
if(!XR_SUCCEEDED(result)){
throw std::runtime_error("Failed to enumerate swapchain images");
}
for(size_t i = 0; i < length; i++){
framebuffers[i].attach_texture(get_texture(i));
}
}
Openxr_swapchain xr_swapchain;
std::vector<Framebuffer> framebuffers;
std::vector<XrSwapchainImageOpenGLKHR> images;
};
class Openxr_local_space {
public:
Openxr_local_space(XrSession session){
XrPosef origin{};
origin.orientation.x = 0.0;
origin.orientation.y = 0.0;
origin.orientation.z = 0.0;
origin.orientation.w = 1.0;
XrReferenceSpaceCreateInfo space_create_info;
space_create_info.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO;
space_create_info.next = NULL;
space_create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL;
space_create_info.poseInReferenceSpace = origin;
XrResult result = xrCreateReferenceSpace(session, &space_create_info, &space);
if(!XR_SUCCEEDED(result)){
throw std::runtime_error("Failed to create OpenXR reference space!");
}
}
~Openxr_local_space(){
xrDestroySpace(space);
}
Openxr_local_space(const Openxr_local_space&) = delete;
Openxr_local_space(Openxr_local_space&&) = delete;
Openxr_local_space& operator=(const Openxr_local_space&) = delete;
Openxr_local_space& operator=(Openxr_local_space&&) = delete;
XrSpace get() { return space; }
private:
XrSpace space;
};
struct Openxr_state{
Openxr_instance instance;
XrSystemId system_id;
//Openxr_session session;
};
struct state_xrgl{
video_desc current_desc;
int buffered_frames_count;
Sdl_window window;
Openxr_state xr_state;
PanoramaScene scene;
std::chrono::steady_clock::time_point last_frame;
std::mutex lock;
std::condition_variable frame_consumed_cv;
std::condition_variable new_frame_ready_cv;
std::queue<video_frame *> frame_queue;
std::vector<video_frame *> free_frame_pool;
std::condition_variable free_frame_ready_cv;
std::vector<video_frame *> dispose_frame_pool;
};
static std::vector<XrViewConfigurationView> get_views(Openxr_state& xr_state){
unsigned view_count;
XrResult result = xrEnumerateViewConfigurationViews(xr_state.instance.get(),
xr_state.system_id,
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
0,
&view_count,
nullptr);
if(!XR_SUCCEEDED(result)){
throw std::runtime_error("Failed to enumerate view configuration views!");
}
std::vector<XrViewConfigurationView> config_views(view_count);
for(auto& view : config_views) view.type = XR_TYPE_VIEW_CONFIGURATION_VIEW;
result = xrEnumerateViewConfigurationViews(xr_state.instance.get(),
xr_state.system_id,
XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO,
view_count,
&view_count,
config_views.data());
if(!XR_SUCCEEDED(result)){
throw std::runtime_error("Failed to enumerate view configuration views!");
}
return config_views;
}
static std::vector<int64_t> get_swapchain_formats(XrSession session){
XrResult result;
unsigned swapchain_format_count;
result = xrEnumerateSwapchainFormats(session,
0,
&swapchain_format_count,
nullptr);
printf("Runtime supports %d swapchain formats\n", swapchain_format_count);
std::vector<int64_t> swapchain_formats(swapchain_format_count);
result = xrEnumerateSwapchainFormats(session,
swapchain_format_count,
&swapchain_format_count,
swapchain_formats.data());
if(!XR_SUCCEEDED(result)){
throw std::runtime_error("Failed to enumerate swapchain formats!");
}
return swapchain_formats;
}
static glm::mat4 get_proj_mat(const XrFovf& fov, float zNear, float zFar){
glm::mat4 res{};
float tanLeft = glm::tan(fov.angleLeft);
float tanRight = glm::tan(fov.angleRight);
float tanUp = glm::tan(fov.angleUp);
float tanDown = glm::tan(fov.angleDown);
res[0][0] = 2.f / (tanRight - tanLeft);
res[1][1] = 2.f / (tanUp - tanDown);
res[2][0] = (tanRight + tanLeft) / (tanRight - tanLeft);
res[2][1] = (tanUp + tanDown) / (tanUp - tanDown);
res[2][2] = -(zFar + zNear) / (zFar - zNear);
res[2][3] = -1;
res[3][2] = -(2.f * zFar * zNear) / (zFar - zNear);
return res;
}
static int64_t select_swapchain_fmt(const std::vector<int64_t>& swapchain_formats){
const int64_t preferred_formats[] = {
GL_RGBA8_EXT,
GL_SRGB8_ALPHA8_EXT
};
for(auto pref : preferred_formats){
for(auto supported_fmt : swapchain_formats){
if(pref == supported_fmt){
return pref;
}
}
}
return 0;
}
static void map_new_buffer(struct video_frame *f){
glBufferData(GL_PIXEL_UNPACK_BUFFER, f->tiles[0].data_len, 0, GL_STREAM_DRAW);
f->tiles[0].data = (char *) glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
GLenum ret = glGetError();
if(ret != GL_NO_ERROR){
std::cerr << "Error mapping: " << ret << std::endl;
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
}
static void recycle_frame(video_frame *f){
GlBuffer *pbo = static_cast<GlBuffer *>(f->callbacks.dispose_udata);
if(!pbo){
return;
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo->get());
if(f->tiles[0].data){
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
}
map_new_buffer(f);
}
static void delete_frame(video_frame *f){
GlBuffer *pbo = static_cast<GlBuffer *>(f->callbacks.dispose_udata);
vf_free(f);
if(!pbo){
return;
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo->get());
if(f->tiles[0].data){
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
}
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
delete pbo;
}
static video_frame *allocate_frame(state_xrgl *s){
video_frame *buffer = vf_alloc_desc(s->current_desc);
GlBuffer *pbo = new GlBuffer();
buffer->callbacks.dispose_udata = pbo;
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo->get());
map_new_buffer(buffer);
return buffer;
}
static void worker(state_xrgl *s){
PROFILE_FUNC;
s->window.make_worker_context_current();
std::unique_lock<std::mutex> lk(s->lock);
for(size_t i = 0; i < MAX_BUFFER_SIZE; i++){
video_frame *buf = vf_alloc(1);
GlBuffer *pbo = new GlBuffer();
buf->callbacks.dispose_udata = pbo;
s->free_frame_pool.push_back(buf);
}
lk.unlock();
s->free_frame_ready_cv.notify_all();
bool running = true;
lk.lock();
while(running){
PROFILE_DETAIL("Wait for frame");
s->new_frame_ready_cv.wait(lk,
[s]{
return !s->frame_queue.empty() || !s->dispose_frame_pool.empty();
});
if(!s->dispose_frame_pool.empty()){
PROFILE_DETAIL("Process disposed frames");
for(video_frame *f : s->dispose_frame_pool){
if (video_desc_eq(video_desc_from_frame(f), s->current_desc)) {
recycle_frame(f);
s->free_frame_pool.push_back(f);
} else {
delete_frame(f);
struct video_frame *buffer = allocate_frame(s);
s->free_frame_pool.push_back(buffer);
}
}
s->dispose_frame_pool.clear();
s->free_frame_ready_cv.notify_all();
}
if(!s->frame_queue.empty()){
PROFILE_DETAIL("put_frame");
video_frame *frame = s->frame_queue.front();
s->frame_queue.pop();
lk.unlock();
if(!frame){
SDL_Event event;
event.type = SDL_QUIT;
SDL_PushEvent(&event);
running = false;
} else {
s->frame_consumed_cv.notify_one();
s->scene.put_frame(frame, frame->callbacks.dispose_udata != nullptr);
lk.lock();
s->dispose_frame_pool.push_back(frame);
}
}
}
}
static void display_xrgl_run(void *state){
PROFILE_FUNC;
state_xrgl *s = static_cast<state_xrgl *>(state);
//Make sure the worker context is initialized before starting worker
s->window.make_worker_context_current();
s->window.make_render_context_current();
std::thread worker_thread(worker, s);
#ifdef __linux__
XrGraphicsBindingOpenGLXlibKHR graphics_binding_gl = {};
graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_XLIB_KHR;
graphics_binding_gl.xDisplay = nullptr;
s->window.getXlibHandles(&graphics_binding_gl.xDisplay,
&graphics_binding_gl.glxContext,
&graphics_binding_gl.glxDrawable);
Openxr_session session(s->xr_state.instance.get(),
s->xr_state.system_id,
&graphics_binding_gl);
#elif defined _WIN32
XrGraphicsBindingOpenGLWin32KHR graphics_binding_gl = {};
graphics_binding_gl.type = XR_TYPE_GRAPHICS_BINDING_OPENGL_WIN32_KHR;
graphics_binding_gl.next = nullptr;
s->window.make_render_context_current();
graphics_binding_gl.hDC = wglGetCurrentDC();
graphics_binding_gl.hGLRC = wglGetCurrentContext();
Openxr_session session(s->xr_state.instance.get(),
s->xr_state.system_id,
&graphics_binding_gl);
#else
//TODO: Implement GL OpenXR binding for other platforms
log_msg(LOG_LEVEL_ERROR, "OpenXR and OpenGL binding not implemented on this platform!\n");
return;
#endif // defined __linux__
std::vector<XrViewConfigurationView> config_views = get_views(s->xr_state);
Openxr_local_space space(session.get());
session.begin();
std::vector<int64_t> swapchain_fmts = get_swapchain_formats(session.get());
int64_t selected_swapchain_fmt = select_swapchain_fmt(swapchain_fmts);
std::vector<Gl_interop_swapchain> swapchains;
for(const auto& view : config_views){
swapchains.emplace_back(session.get(),
selected_swapchain_fmt,
view.recommendedImageRectWidth,
view.recommendedImageRectHeight);
}
size_t view_count = config_views.size();
std::vector<XrCompositionLayerProjectionView> projection_views(view_count);
XrCompositionLayerProjection projection_layer;
projection_layer.type = XR_TYPE_COMPOSITION_LAYER_PROJECTION;
projection_layer.next = nullptr;
projection_layer.layerFlags = 0;
projection_layer.space = space.get();
projection_layer.viewCount = view_count;
projection_layer.views = projection_views.data();
std::vector<XrView> views(view_count);
for(auto& view : views){
view.type = XR_TYPE_VIEW;
view.next = nullptr;
}
for(unsigned i = 0; i < view_count; i++){
projection_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW;
projection_views[i].next = nullptr;
projection_views[i].subImage.swapchain = swapchains[i].get();
projection_views[i].subImage.imageArrayIndex = 0;
projection_views[i].subImage.imageRect.offset.x = 0;
projection_views[i].subImage.imageRect.offset.y = 0;
projection_views[i].subImage.imageRect.extent.width =
config_views[i].recommendedImageRectWidth;
projection_views[i].subImage.imageRect.extent.height =
config_views[i].recommendedImageRectHeight;
}
bool running = true;
if(selected_swapchain_fmt == GL_SRGB8_ALPHA8_EXT){
/* Convert to sRGB to correctly display on the HMD.
* This however breaks the preview window colors, because
* the framebuffer is directly copied into preview window
* using glBlitNamedFramebuffer
*/
glEnable(GL_FRAMEBUFFER_SRGB);
}
glm::mat4 view_reset_rot = glm::mat4(1.0f);
while(running){
XrResult result;
XrFrameState frame_state{};
frame_state.type = XR_TYPE_FRAME_STATE;
frame_state.next = nullptr;
XrFrameWaitInfo frame_wait_info{};
frame_wait_info.type = XR_TYPE_FRAME_WAIT_INFO;
frame_wait_info.next = nullptr;
PROFILE_DETAIL("wait frame");
result = xrWaitFrame(session.get(), &frame_wait_info, &frame_state);
if (!XR_SUCCEEDED(result)){
log_msg(LOG_LEVEL_ERROR, "Failed to xrWaitFrame\n");
break;
}
bool reset_view = false;
SDL_Event event;
while(SDL_PollEvent(&event)){
switch(event.type){
case SDL_QUIT:
running = false;
break;
case SDL_WINDOWEVENT:
if(event.window.event == SDL_WINDOWEVENT_RESIZED){
s->window.width = event.window.data1;
s->window.height = event.window.data2;
}
break;
case SDL_KEYDOWN:
case SDL_KEYUP:
switch(event.key.keysym.sym){
case SDLK_q:
running = false;
break;
case SDLK_v:
if(event.type == SDL_KEYUP){
reset_view = true;
}
break;
}
break;
default:
break;
}
}
while(true){
XrEventDataBuffer xr_event{};
xr_event.type = XR_TYPE_EVENT_DATA_BUFFER;
xr_event.next = nullptr;
result = xrPollEvent(s->xr_state.instance.get(), &xr_event);
if(result != XR_SUCCESS){
break;
}
switch(xr_event.type){
case XR_TYPE_EVENT_DATA_EVENTS_LOST:
log_msg(LOG_LEVEL_WARNING, "OpenXR events lost!\n");
break;
case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING:
log_msg(LOG_LEVEL_WARNING, "OpenXR instance loss pending!\n");
running = false;
break;
case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED:
{
XrEventDataSessionStateChanged* event = (XrEventDataSessionStateChanged*) &xr_event;
if(event->state >= XR_SESSION_STATE_STOPPING){
log_msg(LOG_LEVEL_NOTICE, "Received event requesting stop\n");
running = false;
}
break;
}
default:
log_msg(LOG_LEVEL_NOTICE, "Unhandled event %d\n", xr_event.type);
break;
}
}
XrViewLocateInfo view_locate_info;
view_locate_info.type = XR_TYPE_VIEW_LOCATE_INFO;
view_locate_info.displayTime = frame_state.predictedDisplayTime;
view_locate_info.viewConfigurationType = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; //Assuming stereo view exists. TODO: do it properly and enumerate types first
view_locate_info.space = space.get();
XrViewState view_state;
view_state.type = XR_TYPE_VIEW_STATE;
view_state.next = nullptr;
uint32_t located_views = 0;
result = xrLocateViews(session.get(),
&view_locate_info,
&view_state,
view_count,
&located_views,
views.data());
if (!XR_SUCCEEDED(result)){
log_msg(LOG_LEVEL_ERROR, "Failed to locate views!\n");
break;
}
/*
printf("View: %f %f %f %f, %f %f %f, fov = %f %f %f %f\n",
views[1].pose.orientation.x,
views[1].pose.orientation.y,
views[1].pose.orientation.z,
views[1].pose.orientation.w,
views[1].pose.position.x,
views[1].pose.position.y,
views[1].pose.position.z,
views[1].fov.angleLeft,
views[1].fov.angleRight,
views[1].fov.angleUp,
views[1].fov.angleDown);
*/
XrFrameBeginInfo frame_begin_info;
frame_begin_info.type = XR_TYPE_FRAME_BEGIN_INFO;
frame_begin_info.next = nullptr;
PROFILE_DETAIL("begin frame");
result = xrBeginFrame(session.get(), &frame_begin_info);
if (!XR_SUCCEEDED(result)){
log_msg(LOG_LEVEL_ERROR, "Failed to begin frame!\n");
break;
}
for(unsigned i = 0; i < view_count; i++){
PROFILE_DETAIL("render view");
XrSwapchainImageAcquireInfo swapchain_image_acquire_info;
swapchain_image_acquire_info.type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO;
swapchain_image_acquire_info.next = nullptr;
uint32_t buf_idx;
result = xrAcquireSwapchainImage(
swapchains[i].get(),
&swapchain_image_acquire_info,
&buf_idx);
if(!XR_SUCCEEDED(result)){
log_msg(LOG_LEVEL_ERROR, "Failed to acquire swapchain image!\n");
break;
}
XrSwapchainImageWaitInfo swapchain_image_wait_info;
swapchain_image_wait_info.type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO;
swapchain_image_wait_info.next = nullptr;
swapchain_image_wait_info.timeout = 1000;
result = xrWaitSwapchainImage(swapchains[i].get(), &swapchain_image_wait_info);
if(!XR_SUCCEEDED(result)){
log_msg(LOG_LEVEL_ERROR, "failed to wait for swapchain image!\n");
break;
}
projection_views[i].pose = views[i].pose;
projection_views[i].fov = views[i].fov;
unsigned w = config_views[i].recommendedImageRectWidth;
unsigned h = config_views[i].recommendedImageRectHeight;
glm::mat4 projMat = get_proj_mat(views[i].fov, 0.05f, 100.f);
//glm::mat4 projMat = glm::perspective(glm::radians(70.f), (float) w /h, 0.1f, 300.f);
const auto& rot = views[i].pose.orientation;
glm::mat4 viewMat = glm::mat4_cast(glm::quat(rot.w, rot.x, rot.y, rot.z));
if(reset_view){
reset_view = false;
view_reset_rot = viewMat;
}
glm::mat4 pvMat = projMat * glm::inverse(viewMat) * view_reset_rot;
auto framebuffer = swapchains[i].get_framebuffer(buf_idx).get();
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
glClear(GL_COLOR_BUFFER_BIT);
s->scene.render(w, h, pvMat);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
if(i % 2){
int dstX0;
int dstY0;
int dstX1;
int dstY1;
if(w * s->window.height > s->window.width * h){
dstX0 = 0;
dstX1 = s->window.width;
int height = (s->window.width * h) / w;
dstY0 = (s->window.height - height) / 2;
dstY1 = dstY0 + height;
} else {
int width = (s->window.height * w) / h;
dstX0 = (s->window.width - width) / 2;
dstX1 = dstX0 + width;
dstY0 = 0;
dstY1 = s->window.height;
}
glClear(GL_COLOR_BUFFER_BIT);
glBlitNamedFramebuffer((GLuint)framebuffer, // readFramebuffer
(GLuint)0, // backbuffer // drawFramebuffer
(GLint)0, // srcX0
(GLint)0, // srcY0
(GLint)w, // srcX1
(GLint)h, // srcY1
(GLint)dstX0, // dstX0
(GLint)dstY0, // dstY0
(GLint)dstX1, // dstX1
(GLint)dstY1, // dstY1
(GLbitfield)GL_COLOR_BUFFER_BIT, // mask
(GLenum)GL_LINEAR); // filter
SDL_GL_SwapWindow(s->window.sdl_window);
}
PROFILE_DETAIL("glFinish");
glFinish();
PROFILE_DETAIL("release swapchain");
XrSwapchainImageReleaseInfo swapchain_image_release_info;
swapchain_image_release_info.type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO;
swapchain_image_release_info.next = nullptr;
result = xrReleaseSwapchainImage(
swapchains[i].get(),
&swapchain_image_release_info);
if (!XR_SUCCEEDED(result)){
log_msg(LOG_LEVEL_ERROR, "Failed to release swapchain image!\n");
break;
}
}
const XrCompositionLayerBaseHeader *composition_layers = (const XrCompositionLayerBaseHeader *) &projection_layer;
XrFrameEndInfo frame_end_info;
frame_end_info.type = XR_TYPE_FRAME_END_INFO;
frame_end_info.displayTime = frame_state.predictedDisplayTime;
frame_end_info.layerCount = 1;
frame_end_info.layers = &composition_layers;
frame_end_info.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE;
frame_end_info.next = nullptr;
PROFILE_DETAIL("End Frame");
result = xrEndFrame(session.get(), &frame_end_info);
if (!XR_SUCCEEDED(result)){
log_msg(LOG_LEVEL_ERROR, "Failed to end frame!\n");
break;
}
}
exit_uv(0);
worker_thread.join();
}
static void * display_xrgl_init(struct module *parent, const char *fmt, unsigned int flags) {
UNUSED(parent);
UNUSED(fmt);
UNUSED(flags);
state_xrgl *s = new state_xrgl();
XrSystemGetInfo system_get_info;
system_get_info.type = XR_TYPE_SYSTEM_GET_INFO;
system_get_info.next = nullptr;
system_get_info.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
XrResult result = xrGetSystem(s->xr_state.instance.get(),
&system_get_info,
&s->xr_state.system_id);
if(!XR_SUCCEEDED(result)){
throw std::runtime_error("No available device found!");
}
s->free_frame_pool.reserve(MAX_BUFFER_SIZE);
s->dispose_frame_pool.reserve(MAX_BUFFER_SIZE);
return s;
}
static void display_xrgl_done(void *state) {
state_xrgl *s = static_cast<state_xrgl *>(state);
delete s;
}
static struct video_frame * display_xrgl_getf(void *state) {
struct state_xrgl *s = static_cast<state_xrgl *>(state);
std::unique_lock<std::mutex> lock(s->lock);
while (true) {
s->free_frame_ready_cv.wait(lock, [s]{return s->free_frame_pool.size() > 0;});
struct video_frame *buffer = s->free_frame_pool.back();
s->free_frame_pool.pop_back();
if (video_desc_eq(video_desc_from_frame(buffer), s->current_desc)) {
return buffer;
} else {
s->dispose_frame_pool.push_back(buffer);
s->new_frame_ready_cv.notify_one();
}
}
}
static bool display_xrgl_putf(void *state, struct video_frame *frame, long long nonblock) {
struct state_xrgl *s = static_cast<state_xrgl *>(state);
std::unique_lock<std::mutex> lk(s->lock);
if(!frame) {
s->frame_queue.push(frame);
lk.unlock();
s->new_frame_ready_cv.notify_one();
return true;
}
if (nonblock == PUTF_DISCARD) {
s->dispose_frame_pool.push_back(frame);
s->new_frame_ready_cv.notify_one();
return true;
}
if (s->frame_queue.size() >= MAX_BUFFER_SIZE && nonblock != PUTF_BLOCKING) {
s->dispose_frame_pool.push_back(frame);
s->new_frame_ready_cv.notify_one();
return false;
}
s->frame_consumed_cv.wait(lk, [s]{return s->frame_queue.size() < MAX_BUFFER_SIZE;});
s->frame_queue.push(frame);
lk.unlock();
s->new_frame_ready_cv.notify_one();
return true;
}
static bool display_xrgl_reconfigure(void *state, struct video_desc desc) {
state_xrgl *s = static_cast<state_xrgl *>(state);
s->current_desc = desc;
return true;
}
static bool display_xrgl_get_property(void *state, int property, void *val, size_t *len) {
auto s = static_cast<state_xrgl *>(state);
enum interlacing_t supported_il_modes[] = {PROGRESSIVE};
int rgb_shift[] = {0, 8, 16};
switch (property) {
case DISPLAY_PROPERTY_CODECS:{
auto codecs = s->scene.get_codecs();
size_t codecs_size = codecs.size() * sizeof(codec_t);
if(codecs_size <= *len) {
memcpy(val, codecs.data(), codecs_size);
} else {
return false;
}
*len = codecs_size;
break;
}
case DISPLAY_PROPERTY_RGB_SHIFT:
if(sizeof(rgb_shift) > *len) {
return false;
}
memcpy(val, rgb_shift, sizeof(rgb_shift));
*len = sizeof(rgb_shift);
break;
case DISPLAY_PROPERTY_BUF_PITCH:
*(int *) val = PITCH_DEFAULT;
*len = sizeof(int);
break;
case DISPLAY_PROPERTY_SUPPORTED_IL_MODES:
if(sizeof(supported_il_modes) <= *len) {
memcpy(val, supported_il_modes, sizeof(supported_il_modes));
} else {
return false;
}
*len = sizeof(supported_il_modes);
break;
default:
return false;
}
return true;
}
static void display_xrgl_probe(struct device_info **available_cards, int *count, void (**deleter)(void *)){
UNUSED(deleter);
*count = 0;
*available_cards = nullptr;
XrSystemGetInfo systemGetInfo;
systemGetInfo.type = XR_TYPE_SYSTEM_GET_INFO;
systemGetInfo.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY;
systemGetInfo.next = NULL;
XrSystemId systemId;
XrSystemProperties systemProperties;
systemProperties.type = XR_TYPE_SYSTEM_PROPERTIES;
systemProperties.next = NULL;
systemProperties.graphicsProperties = {};
systemProperties.trackingProperties = {};
try{
Openxr_instance instance;
XrResult result = xrGetSystem(instance.get(), &systemGetInfo, &systemId);
if(!XR_SUCCEEDED(result)){
return;
}
result = xrGetSystemProperties(instance.get(), systemId, &systemProperties);
if(!XR_SUCCEEDED(result)){
return;
}
} catch(const std::exception& e){
std::cout << e.what() << std::endl;
return;
}
*available_cards = (struct device_info *) calloc(1, sizeof(struct device_info));
*count = 1;
snprintf((*available_cards)[0].dev, sizeof((*available_cards)[0].dev), ":system=%lu", systemId);
snprintf((*available_cards)[0].name, sizeof((*available_cards)[0].name), "OpenXr: %s", systemProperties.systemName);
(*available_cards)[0].repeatable = false;
}
static const struct video_display_info openxr_gl_info = {
display_xrgl_probe,
display_xrgl_init,
display_xrgl_run,
display_xrgl_done,
display_xrgl_getf,
display_xrgl_putf,
display_xrgl_reconfigure,
display_xrgl_get_property,
NULL,
NULL,
DISPLAY_NO_GENERIC_FPS_INDICATOR,
};
REGISTER_MODULE(openxr_gl, &openxr_gl_info, LIBRARY_CLASS_VIDEO_DISPLAY, VIDEO_DISPLAY_ABI_VERSION);