aboutsummaryrefslogtreecommitdiff
path: root/src/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cpp')
-rw-r--r--src/main.cpp1125
1 files changed, 966 insertions, 159 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 0278da1..7e13ec9 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -33,6 +33,8 @@
#include <GL/glew.h>
#include "../include/window_texture.h"
+#include "../include/mpv.hpp"
+#include "../include/config.hpp"
#include <SDL.h>
#include <SDL_opengl.h>
@@ -47,6 +49,8 @@
#include <X11/Xlib.h>
#include <X11/extensions/Xfixes.h>
+#include <xdo.h>
+
#include <stdio.h>
#include <string>
#include <cstdlib>
@@ -60,17 +64,120 @@
#include <fstream>
#include <sstream>
+#include <thread>
+#include <mutex>
+#include <condition_variable>
+
#ifndef _countof
#define _countof(x) (sizeof(x)/sizeof((x)[0]))
#endif
-void ThreadSleep( unsigned long nMilliseconds )
+// Not in public headers in older version of openvr.
+namespace vr
{
- usleep( nMilliseconds * 1000 );
+ const VROverlayFlags vrvid_VROverlayFlags_EnableControlBar = (VROverlayFlags)(1 << 23);
+ const VROverlayFlags vrvid_VROverlayFlags_EnableControlBarKeyboard = (VROverlayFlags)(1 << 24);
+ const VROverlayFlags vrvid_VROverlayFlags_EnableControlBarClose = (VROverlayFlags)(1 << 25);
}
static bool g_bPrintf = true;
+enum class ViewMode {
+ LEFT_RIGHT,
+ RIGHT_LEFT,
+ PLANE,
+ SPHERE360
+};
+
+enum class ProjectionMode {
+ SPHERE,
+ FLAT,
+ CYLINDER, /* aka plane */
+ SPHERE360
+};
+#define BUFFER_DEPTH 2
+class VideoBuffers
+{
+public:
+ VideoBuffers(int nWidth, int nHeight);
+ ~VideoBuffers();
+
+
+ GLuint get_renderTextureId();
+ GLuint get_renderFramebufferId();
+ GLuint get_showTextureId();
+ GLuint get_showFramebufferId();
+
+ void swap_buffer();
+
+ std::mutex swap_mutex;
+private:
+ unsigned int current_show_frame = 0;
+
+ GLuint texture_id[BUFFER_DEPTH];
+ GLuint frame_buffer_id[BUFFER_DEPTH];
+};
+
+VideoBuffers::VideoBuffers(int nWidth, int nHeight)
+{
+ for(int i = 0; i < BUFFER_DEPTH; i++)
+ {
+ glGenFramebuffers(1, &frame_buffer_id[i]);
+ glBindFramebuffer(GL_FRAMEBUFFER, frame_buffer_id[i]);
+
+ glGenTextures(1, &texture_id[i]);
+ glBindTexture(GL_TEXTURE_2D, texture_id[i]);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, nWidth, nHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id[i], 0);
+ }
+
+ // check FBO status
+ GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+ if (status != GL_FRAMEBUFFER_COMPLETE)
+ {
+ throw std::runtime_error("Error: Failed to create video textures and buffers.");
+ }
+
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+}
+
+VideoBuffers::~VideoBuffers()
+{
+ for(int i = 0; i < BUFFER_DEPTH; i++)
+ {
+ glDeleteTextures(1, &texture_id[i]);
+ glDeleteFramebuffers(1, &frame_buffer_id[i]);
+ }
+}
+
+GLuint VideoBuffers::get_renderTextureId()
+{
+ return texture_id[(current_show_frame + 1) % BUFFER_DEPTH];
+}
+
+GLuint VideoBuffers::get_renderFramebufferId()
+{
+ return frame_buffer_id[(current_show_frame + 1) % BUFFER_DEPTH];
+}
+
+GLuint VideoBuffers::get_showTextureId()
+{
+ return texture_id[current_show_frame];
+}
+
+GLuint VideoBuffers::get_showFramebufferId()
+{
+ return frame_buffer_id[current_show_frame];
+}
+
+void VideoBuffers::swap_buffer()
+{
+ std::lock_guard<std::mutex> lock(swap_mutex);
+ current_show_frame = (current_show_frame + 1) % BUFFER_DEPTH;
+}
+
//-----------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
@@ -83,6 +190,7 @@ public:
bool BInit();
bool BInitGL();
bool BInitCompositor();
+ bool BInitOverlay();
void Shutdown();
@@ -107,6 +215,11 @@ public:
void RenderStereoTargets();
void RenderCompanionWindow();
void RenderScene( vr::Hmd_Eye nEye );
+ void RenderOverlay();
+
+ void UpdateOverlayTitle();
+ void UpdateOverlayIcon();
+ unsigned long FindBestOverlayIcon();
glm::mat4 GetHMDMatrixProjectionEye( vr::Hmd_Eye nEye );
glm::mat4 GetHMDMatrixPoseEye( vr::Hmd_Eye nEye );
@@ -122,10 +235,13 @@ public:
// Get focused window or None
Window get_focused_window();
+ void save_config();
+
+ int exit_code = 0;
+ bool bQuit = false;
+ bool bQuitSignal = false;
private:
bool m_bDebugOpenGL;
- bool m_bVerbose;
- bool m_bPerf;
bool m_bVblank;
bool m_bGlFinishHack;
@@ -139,6 +255,7 @@ private: // SDL bookkeeping
uint32_t m_nCompanionWindowHeight;
SDL_GLContext m_pContext;
+ SDL_GLContext m_pMpvContext;
private: // OpenGL bookkeeping
int m_iTrackedControllerCount;
@@ -146,7 +263,6 @@ private: // OpenGL bookkeeping
int m_iValidPoseCount;
int m_iValidPoseCount_Last;
bool m_bResetRotation;
- glm::vec2 m_vAnalogValue;
std::string m_strPoseClasses; // what classes we saw poses for this frame
char m_rDevClassChar[ vr::k_unMaxTrackedDeviceCount ]; // for each device, a character representing its class
@@ -171,15 +287,10 @@ private: // OpenGL bookkeeping
GLuint m_glCompanionWindowIDIndexBuffer;
unsigned int m_uiCompanionWindowIndexSize;
- GLuint m_glControllerVertBuffer;
- GLuint m_unControllerVAO;
- unsigned int m_uiControllerVertcount;
-
glm::mat4 m_mat4HMDPose;
glm::mat4 m_mat4eyePosLeft;
glm::mat4 m_mat4eyePosRight;
- glm::mat4 m_mat4ProjectionCenter;
glm::mat4 m_mat4ProjectionLeft;
glm::mat4 m_mat4ProjectionRight;
@@ -188,6 +299,8 @@ private: // OpenGL bookkeeping
glm::quat hmd_rot = glm::quat(0.0f, 0.0f, 0.0f, 1.0f);
glm::quat m_reset_rotation = glm::quat(0.0f, 0.0f, 0.0f, 0.0f);
+ Config config;
+
struct VertexDataScene
{
glm::vec3 position;
@@ -224,16 +337,18 @@ private: // OpenGL bookkeeping
FramebufferDesc leftEyeDesc;
FramebufferDesc rightEyeDesc;
+ //FramebufferDesc mpvDesc;
+ VideoBuffers* mpvBuffers = nullptr;
+
bool CreateFrameBuffer( int nWidth, int nHeight, FramebufferDesc &framebufferDesc );
+ void set_current_context(SDL_GLContext context);
+ bool take_render_update();
+ void set_render_update();
uint32_t m_nRenderWidth;
uint32_t m_nRenderHeight;
vr::VRActionHandle_t m_actionHideCubes = vr::k_ulInvalidActionHandle;
- vr::VRActionHandle_t m_actionHideThisController = vr::k_ulInvalidActionHandle;
- vr::VRActionHandle_t m_actionTriggerHaptic = vr::k_ulInvalidActionHandle;
- vr::VRActionHandle_t m_actionAnalongInput = vr::k_ulInvalidActionHandle;
-
vr::VRActionSetHandle_t m_actionsetDemo = vr::k_ulInvalidActionSetHandle;
private: // X compositor
@@ -244,11 +359,25 @@ private: // X compositor
bool follow_focused = false;
bool focused_window_changed = true;
bool focused_window_set = false;
-
- int mouse_x;
- int mouse_y;
- int window_width;
- int window_height;
+ const char *mpv_file = nullptr;
+ const char *mpv_profile = "gpu-hq";
+ Mpv mpv;
+ std::mutex mpv_render_update_mutex;
+ std::condition_variable mpv_render_update_condition;
+ bool mpv_render_update = false;
+ int64_t mpv_video_width = 0;
+ int64_t mpv_video_height = 0;
+ bool mpv_video_loaded = false;
+ bool mpv_loaded_in_thread = false;
+ bool running = true;
+ std::mutex context_mutex;
+
+ std::thread mpv_thread;
+
+ int mouse_x = 0;
+ int mouse_y = 0;
+ int window_width = 1;
+ int window_height = 1;
Uint32 window_resize_time;
bool window_resized = false;
@@ -258,22 +387,8 @@ private: // X compositor
int x_fixes_error_base;
int prev_visibility_state = VisibilityFullyObscured;
- GLint pixmap_texture_width = 0;
- GLint pixmap_texture_height = 0;
-
- enum class ViewMode {
- LEFT_RIGHT,
- RIGHT_LEFT,
- PLANE,
- SPHERE360
- };
-
- enum class ProjectionMode {
- SPHERE,
- FLAT,
- CYLINDER, /* aka plane */
- SPHERE360
- };
+ GLint pixmap_texture_width = 1;
+ GLint pixmap_texture_height = 1;
ProjectionMode projection_mode = ProjectionMode::SPHERE;
double zoom = 0.0;
@@ -283,6 +398,7 @@ private: // X compositor
bool cursor_wrap = true;
bool free_camera = false;
bool reduce_flicker = false;
+ bool use_system_mpv_config = false;
double reduce_flicker_counter = 0.0;
GLuint arrow_image_texture_id = 0;
@@ -294,6 +410,20 @@ private: // X compositor
float cursor_scale_uniform[2];
double arrow_ratio;
bool cursor_image_set = false;
+
+ bool config_exists = false;
+
+ bool overlay_mode = false;
+ vr::VROverlayHandle_t overlay_handle = vr::k_ulOverlayHandleInvalid;
+ vr::VROverlayHandle_t thumbnail_handle = vr::k_ulOverlayHandleInvalid;
+ VideoBuffers *overlay_buffers = nullptr;
+ GLuint m_unOverlayProgramID = 0;
+ const char *overlay_key = "vr-video-player";
+ float overlay_width = 2.5f;
+ xdo_t *overlay_xdo = nullptr;
+ Atom overlay_icon_atom;
+ bool overlay_mouse_controls = true;
+ GLint m_unOverlayTextureLoc;
};
@@ -383,25 +513,33 @@ void dprintf( const char *fmt, ... )
}
static void usage() {
- fprintf(stderr, "usage: vr-video-player [--sphere|--sphere360|--flat|--plane] [--left-right|--right-left] [--stretch|--no-stretch] [--zoom zoom-level] [--cursor-scale scale] [--cursor-wrap|--no-cursor-wrap] [--follow-focused] <window_id>\n");
+ fprintf(stderr, "usage: vr-video-player [--sphere|--sphere360|--flat|--plane] [--left-right|--right-left] [--stretch|--no-stretch] [--zoom zoom-level] [--cursor-scale scale] [--cursor-wrap|--no-cursor-wrap] [--follow-focused|--video video|<window_id>] [--use-system-mpv-config] [--mpv-profile <profile>] [--free-camera] [--reduce-flicker] [--overlay] [--overlay-key <key>] [--overlay-mouse|--no-overlay-mouse] [--overlay-width <width>]\n");
fprintf(stderr, "\n");
fprintf(stderr, "OPTIONS\n");
- fprintf(stderr, " --sphere View the window as a stereoscopic 180 degrees screen (half sphere). The view will be attached to your head in vr. This is recommended for 180 degrees videos. This is the default value\n");
- fprintf(stderr, " --sphere360 View the window as an equirectangular cube map. This is what is mostly used on youtube, where the video is split into top and bottom as a cubemap. The view will be attached to your head in vr\n");
- fprintf(stderr, " --flat View the window as a stereoscopic flat screen. This is recommended for stereoscopic videos and games\n");
- fprintf(stderr, " --left-right This option is used together with --flat, to specify if the left side of the window is meant to be viewed with the left eye and the right side is meant to be viewed by the right eye. This is the default value\n");
- fprintf(stderr, " --right-left This option is used together with --flat, to specify if the left side of the window is meant to be viewed with the right eye and the right side is meant to be viewed by the left eye\n");
- fprintf(stderr, " --plane View the window as a slightly curved screen. This is recommended for non-stereoscopic content\n");
- fprintf(stderr, " --stretch This option is used together with --flat, To specify if the size of both sides of the window should be combined and stretch to that size when viewed in vr. This is the default value\n");
- fprintf(stderr, " --no-stretch This option is used together with --flat, To specify if the size of one side of the window should be the size of the whole window when viewed in vr. This is the option you want if the window looks too wide\n");
- fprintf(stderr, " --zoom Change the distance to the window. This should be a positive value. In flat and plane modes, this is the distance to the window when the window is reset (with W key or controller trigger button). The default value is 0 for all modes except sphere mode, where the default value is 1. This value is unused for sphere360 mode\n");
- fprintf(stderr, " --cursor-scale Change the size of the cursor. This should be a positive value. If set to 0, then the cursor is hidden. The default value is 1 for all modes except sphere mode, where the default value is 0. The cursor is always hidden in sphere360 mode\n");
- fprintf(stderr, " --cursor-wrap If this option is set, then the cursor position in the vr view will wrap around when it reached the center of the window (i.e when it reaches the edge of one side of the stereoscopic view). This option is only valid for stereoscopic view (flat and sphere modes)\n");
- fprintf(stderr, " --no-cursor-wrap If this option is set, then the cursor position in the vr view will match the the real cursor position inside the window\n");
- fprintf(stderr, " --follow-focused If this option is set, then the selected window will be the focused window. vr-video-player will automatically update when the focused window changes. Either this option or window_id should be used\n");
- fprintf(stderr, " --free-camera If this option is set, then the camera wont follow your position. This option is only applicable when not using --sphere or --sphere360\n");
- fprintf(stderr, " --reduce-flicker A hack to reduce flickering in low resolution text when the headset is not moving by moving the window around quickly by a few pixels\n");
- fprintf(stderr, " window_id The X11 window id of the window to view in vr. This option or --follow-focused is required\n");
+ fprintf(stderr, " --sphere View the window as a stereoscopic 180 degrees screen (half sphere). The view will be attached to your head in vr. This is recommended for 180 degrees videos. This is the default value\n");
+ fprintf(stderr, " --sphere360 View the window as an equirectangular cube map. This is what is mostly used on youtube, where the video is split into top and bottom as a cubemap. The view will be attached to your head in vr\n");
+ fprintf(stderr, " --flat View the window as a stereoscopic flat screen. This is recommended for stereoscopic videos and games\n");
+ fprintf(stderr, " --left-right This option is used together with --flat, to specify if the left side of the window is meant to be viewed with the left eye and the right side is meant to be viewed by the right eye. This is the default value\n");
+ fprintf(stderr, " --right-left This option is used together with --flat, to specify if the left side of the window is meant to be viewed with the right eye and the right side is meant to be viewed by the left eye\n");
+ fprintf(stderr, " --plane View the window as a slightly curved screen. This is recommended for non-stereoscopic content\n");
+ fprintf(stderr, " --stretch This option is used together with --flat, To specify if the size of both sides of the window should be combined and stretch to that size when viewed in vr. This is the default value\n");
+ fprintf(stderr, " --no-stretch This option is used together with --flat, To specify if the size of one side of the window should be the size of the whole window when viewed in vr. This is the option you want if the window looks too wide\n");
+ fprintf(stderr, " --zoom <zoom> Change the distance to the window. This should be a positive value. In flat and plane modes, this is the distance to the window when the window is reset (with W key or controller trigger button). The default value is 0 for all modes except sphere mode, where the default value is 1. This value is unused for sphere360 mode\n");
+ fprintf(stderr, " --cursor-scale <scale> Change the size of the cursor. This should be a positive value. If set to 0, then the cursor is hidden. The default value is 1 for all modes except sphere mode, where the default value is 0. The cursor is always hidden in sphere360 mode\n");
+ fprintf(stderr, " --cursor-wrap If this option is set, then the cursor position in the vr view will wrap around when it reached the center of the window (i.e when it reaches the edge of one side of the stereoscopic view). This option is only valid for stereoscopic view (flat and sphere modes)\n");
+ fprintf(stderr, " --no-cursor-wrap If this option is set, then the cursor position in the vr view will match the the real cursor position inside the window\n");
+ fprintf(stderr, " --reduce-flicker A hack to reduce flickering in low resolution text when the headset is not moving by moving the window around quickly by a few pixels\n");
+ fprintf(stderr, " --free-camera If this option is set, then the camera wont follow your position\n");
+ fprintf(stderr, " --follow-focused If this option is set, then the selected window will be the focused window. vr-video-player will automatically update when the focused window changes. Either this option, --video or window_id should be used\n");
+ fprintf(stderr, " --video <video> Select the video to play (using mpv). Either this option, --follow-focused or window_id should be used\n");
+ fprintf(stderr, " --use-system-mpv-config Use system (~/.config/mpv/mpv.conf) mpv config. Disabled by default\n");
+ fprintf(stderr, " --mpv-profile <profile> Which mpv profile to use. Only applicable when using --video option. Optional, defaults to \"gpu-hq\"\n");
+ fprintf(stderr, " --overlay Run as an OpenVR overlay rather than a standalone application.\n");
+ fprintf(stderr, " --overlay-key <key> Name used to identify the OpenVR overlay. Defaults to \"vr-video-player\".\n");
+ fprintf(stderr, " --overlay-mouse Enable the translation of VR events into mouse events when running as an overlay. This is the default value.\n");
+ fprintf(stderr, " --no-overlay-mouse Disable the translation of VR events into mouse events when running as an overlay.\n");
+ fprintf(stderr, " --overlay-width <width> Overlay width in meters. Defaults to 2.5.\n");
+ fprintf(stderr, " window_id The X11 window id of the window to view in vr. Either this option, --follow-focused or --video should be used\n");
fprintf(stderr, "\n");
fprintf(stderr, "EXAMPLES\n");
fprintf(stderr, " vr-video-player 1830423\n");
@@ -409,29 +547,56 @@ static void usage() {
fprintf(stderr, " vr-video-player --flat --right-left 1830423\n");
fprintf(stderr, " vr-video-player --plane --zoom 2.0 1830423\n");
fprintf(stderr, " vr-video-player --flat $(xdotool selectwindow)\n");
+ fprintf(stderr, " vr-video-player --sphere --video $HOME/Videos/cool-vr-video.mp4\n");
fprintf(stderr, "\n");
fprintf(stderr, "Note: All options except window_id are optional\n");
exit(1);
}
+static void get_config_values(const Config &config, ProjectionMode projection_mode, glm::vec3 &pos, glm::quat &rot, float &zoom) {
+ switch(projection_mode) {
+ case ProjectionMode::SPHERE: {
+ pos = config.sphere.position;
+ rot = config.sphere.rotation;
+ zoom = config.sphere.zoom;
+ break;
+ }
+ case ProjectionMode::FLAT: {
+ pos = config.flat.position;
+ rot = config.flat.rotation;
+ zoom = config.flat.zoom;
+ break;
+ }
+ case ProjectionMode::CYLINDER: {
+ pos = config.plane.position;
+ rot = config.plane.rotation;
+ zoom = config.plane.zoom;
+ break;
+ }
+ case ProjectionMode::SPHERE360: {
+ pos = config.sphere360.position;
+ rot = config.sphere360.rotation;
+ zoom = config.sphere360.zoom;
+ break;
+ }
+ }
+}
+
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CMainApplication::CMainApplication( int argc, char *argv[] )
: m_pCompanionWindow(NULL)
, m_pContext(NULL)
+ , m_pMpvContext(NULL)
, m_nCompanionWindowWidth( 800 )
, m_nCompanionWindowHeight( 600 )
, m_unSceneProgramID( 0 )
, m_unCompanionWindowProgramID( 0 )
, m_pHMD( NULL )
, m_bDebugOpenGL( false )
- , m_bVerbose( false )
- , m_bPerf( false )
, m_bVblank( false )
, m_bGlFinishHack( false )
- , m_glControllerVertBuffer( 0 )
- , m_unControllerVAO( 0 )
, m_unSceneVAO( 0 )
, m_nSceneMatrixLocation( -1 )
, m_nSceneTextureOffsetXLocation( -1 )
@@ -451,6 +616,8 @@ CMainApplication::CMainApplication( int argc, char *argv[] )
bool cursor_scale_set = false;
bool cursor_wrap_set = false;
+ memset(&window_texture, 0, sizeof(window_texture));
+
for(int i = 1; i < argc; ++i) {
if(strcmp(argv[i], "--sphere") == 0) {
if(projection_arg) {
@@ -526,20 +693,57 @@ CMainApplication::CMainApplication( int argc, char *argv[] )
cursor_wrap_set = true;
} else if(strcmp(argv[i], "--follow-focused") == 0) {
if(src_window_id) {
- fprintf(stderr, "Error: --follow-focused option can't be used together with the window_id option\n");
+ fprintf(stderr, "Error: window_id option can't be used together with the --follow-focused option\n");
+ exit(1);
+ }
+ if(mpv_file) {
+ fprintf(stderr, "Error: --video option can't be used together with the --follow-focused option\n");
exit(1);
}
follow_focused = true;
+ } else if(strcmp(argv[i], "--video") == 0 && i < argc - 1) {
+ if(src_window_id) {
+ fprintf(stderr, "Error: --follow-focused option can't be used together with the --video option\n");
+ exit(1);
+ }
+ if(follow_focused) {
+ fprintf(stderr, "Error: window_id option can't be used together with the --video option\n");
+ exit(1);
+ }
+ mpv_file = argv[i + 1];
+ ++i;
+ } else if(strcmp(argv[i], "--use-system-mpv-config") == 0) {
+ use_system_mpv_config = true;
+ } else if(strcmp(argv[i], "--mpv-profile") == 0 && i < argc - 1) {
+ mpv_profile = argv[i + 1];
+ ++i;
} else if(strcmp(argv[i], "--free-camera") == 0) {
free_camera = true;
} else if(strcmp(argv[i], "--reduce-flicker") == 0) {
reduce_flicker = true;
- } else if(argv[i][0] == '-') {
+ } else if(strcmp(argv[i], "--overlay") == 0) {
+ overlay_mode = true;
+ } else if(strcmp(argv[i], "--overlay-key") == 0 && i < argc - 1) {
+ overlay_key = argv[i + 1];
+ ++i;
+ } else if(strcmp(argv[i], "--overlay-width") == 0 && i < argc - 1) {
+ overlay_width = atof(argv[i + 1]);
+ ++i;
+ } else if(strcmp(argv[i], "--overlay-mouse") == 0) {
+ overlay_mouse_controls = true;
+ } else if(strcmp(argv[i], "--no-overlay-mouse") == 0) {
+ overlay_mouse_controls = false;
+ }
+ else if(argv[i][0] == '-') {
fprintf(stderr, "Invalid flag: %s\n", argv[i]);
usage();
} else {
if(follow_focused) {
- fprintf(stderr, "Error: window_id option can't be used together with the --follow-focused option\n");
+ fprintf(stderr, "Error: --follow-focused option can't be used together with the window_id option\n");
+ exit(1);
+ }
+ if(mpv_file) {
+ fprintf(stderr, "Error: --video option can't be used together with the window_id option\n");
exit(1);
}
if (strncmp(argv[i], "window:", 7) == 0) {
@@ -549,8 +753,8 @@ CMainApplication::CMainApplication( int argc, char *argv[] )
}
}
- if(src_window_id == None && !follow_focused) {
- fprintf(stderr, "Missing required window_id flag\n");
+ if(src_window_id == None && !follow_focused && !mpv_file) {
+ fprintf(stderr, "Missing required window_id, --follow-focused or --video option\n");
usage();
}
@@ -580,6 +784,15 @@ CMainApplication::CMainApplication( int argc, char *argv[] )
#ifdef _DEBUG
m_bDebugOpenGL = true;
#endif
+
+ config = read_config(config_exists);
+ if(!config_exists)
+ return;
+
+ float saved_config = 0.0f;
+ get_config_values(config, projection_mode, hmd_pos, m_reset_rotation, saved_config);
+ if(!zoom_set)
+ zoom = saved_config;
};
@@ -649,6 +862,8 @@ static void grabkeys(Display *display) {
XGrabKey(display, XKeysymToKeycode(display, keys[j]), Mod1Mask|modifiers[i], root_window, False, GrabModeAsync, GrabModeAsync);
}
}
+
+ XSync(display, False);
}
@@ -693,7 +908,9 @@ bool CMainApplication::BInit()
// Loading the SteamVR Runtime
vr::EVRInitError eError = vr::VRInitError_None;
- m_pHMD = vr::VR_Init( &eError, vr::VRApplication_Scene );
+
+ vr::EVRApplicationType appType = overlay_mode ? vr::VRApplication_Overlay : vr::VRApplication_Scene;
+ m_pHMD = vr::VR_Init( &eError, appType );
if ( eError != vr::VRInitError_None )
{
@@ -705,11 +922,16 @@ bool CMainApplication::BInit()
}
auto standing_pos = m_pHMD->GetSeatedZeroPoseToStandingAbsoluteTrackingPose();
- hmd_pos += glm::vec3(standing_pos.m[0][3], standing_pos.m[1][3], standing_pos.m[2][3]);
+ if(!config_exists)
+ hmd_pos += glm::vec3(standing_pos.m[0][3], standing_pos.m[1][3], standing_pos.m[2][3]);
int nWindowPosX = 700;
int nWindowPosY = 100;
- Uint32 unWindowFlags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
+ Uint32 unWindowFlags = SDL_WINDOW_OPENGL;
+ if (!overlay_mode)
+ unWindowFlags |= SDL_WINDOW_SHOWN;
+ else
+ unWindowFlags |= SDL_WINDOW_HIDDEN;
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 );
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 2 );
@@ -718,9 +940,13 @@ bool CMainApplication::BInit()
SDL_GL_SetAttribute( SDL_GL_MULTISAMPLEBUFFERS, 0 );
SDL_GL_SetAttribute( SDL_GL_MULTISAMPLESAMPLES, 0 );
+ SDL_GL_SetAttribute( SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1 );
if( m_bDebugOpenGL )
SDL_GL_SetAttribute( SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_DEBUG_FLAG );
+ // Needed for mpv
+ SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "no");
+
m_pCompanionWindow = SDL_CreateWindow( "vr-video-player", nWindowPosX, nWindowPosY, m_nCompanionWindowWidth, m_nCompanionWindowHeight, unWindowFlags );
if (m_pCompanionWindow == NULL)
{
@@ -735,6 +961,20 @@ bool CMainApplication::BInit()
return false;
}
+ if(mpv_file) {
+ m_pMpvContext = SDL_GL_CreateContext(m_pCompanionWindow);
+ if (m_pMpvContext == NULL)
+ {
+ printf( "%s - OpenGL context could not be created! SDL Error: %s\n", __FUNCTION__, SDL_GetError() );
+ return false;
+ }
+ }
+
+ if(SDL_GL_MakeCurrent(m_pCompanionWindow, m_pContext) < 0) {
+ fprintf(stderr, "Failed to make opengl context current, error: %s\n", SDL_GetError());
+ return false;
+ }
+
glewExperimental = GL_TRUE;
GLenum nGlewError = glewInit();
if (nGlewError != GLEW_OK)
@@ -772,36 +1012,105 @@ bool CMainApplication::BInit()
return false;
}
- if (!BInitCompositor())
+ if (overlay_mode) {
+ if (!BInitOverlay()) {
+ printf("%s - Failed to initialize VR Overlay!\n", __FUNCTION__);
+ return false;
+ }
+ }
+ else if (!BInitCompositor())
{
printf("%s - Failed to initialize VR Compositor!\n", __FUNCTION__);
return false;
}
- //char cwd[4096];
- //getcwd(cwd, sizeof(cwd));
- //printf("cwd: %s\n", cwd);
- //dirname(cwd);
+ if(mpv_file) {
+ mpv_thread = std::thread([&]{
+ set_current_context(m_pMpvContext);
+ if(!mpv.create(use_system_mpv_config, mpv_profile))
+ return;
+
+ mpv.load_file(mpv_file);
+ set_current_context(NULL);
+
+ while(running) {
+
+ if(mpv_video_loaded && !mpv_loaded_in_thread) {
+ set_current_context(m_pMpvContext);
+ mpv_loaded_in_thread = true;
+ // TODO: Do not create depth buffer and extra framebuffers
+ //CreateFrameBuffer(mpv_video_width, mpv_video_height, mpvDesc);
+ mpvBuffers = new VideoBuffers(mpv_video_width, mpv_video_height);
+ set_current_context(NULL);
+ }
+
+ if(mpv_video_loaded) {
+ if(take_render_update()) {
+ if(!running)
+ break;
+
+ set_current_context(m_pMpvContext);
+ mpvBuffers->swap_buffer();
+ GLuint current_frame_buffer_id = mpvBuffers->get_renderFramebufferId();
+
+ //glBindFramebuffer( GL_FRAMEBUFFER, mpvDesc.m_nRenderFramebufferId );
+ glBindFramebuffer( GL_FRAMEBUFFER, current_frame_buffer_id );
+ glViewport(0, 0, mpv_video_width, mpv_video_height);
+
+ glDisable(GL_DEPTH_TEST);
+
+ glBindVertexArray( m_unCompanionWindowVAO );
+ glUseProgram( m_unCompanionWindowProgramID );
+
+ //mpv.draw(mpvDesc.m_nRenderFramebufferId, mpv_video_width, mpv_video_height);
+ mpv.draw(current_frame_buffer_id, mpv_video_width, mpv_video_height);
+
+ glBindVertexArray( 0 );
+ glUseProgram( 0 );
+ glBindFramebuffer( GL_FRAMEBUFFER, 0 );
+
+ set_current_context(NULL);
+ }
+/*
+ glDisable( GL_MULTISAMPLE );
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, mpvDesc.m_nRenderFramebufferId );
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mpvDesc.m_nResolveFramebufferId );
+
+ glBlitFramebuffer( 0, 0, mpv_video_width, mpv_video_height, 0, 0, mpv_video_width, mpv_video_height,
+ GL_COLOR_BUFFER_BIT,
+ GL_LINEAR );
+
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0 );
+
+
+ glEnable( GL_MULTISAMPLE );*/
+ } else {
+ usleep(1000);
+ }
+ }
+ delete mpvBuffers;
+ });
+ }
+
char action_manifest_path[PATH_MAX];
realpath("config/hellovr_actions.json", action_manifest_path);
if(access(action_manifest_path, F_OK) == -1) {
strcpy(action_manifest_path, "/usr/share/vr-video-player/hellovr_actions.json");
if(access(action_manifest_path, F_OK) == -1) {
fprintf(stderr, "Unable to find hellovr_action.json!\n");
- exit(1);
+ return false;
}
}
- fprintf(stderr, "Using config file: %s\n", action_manifest_path);
-
- vr::VRInput()->SetActionManifestPath(action_manifest_path);
+ fprintf(stderr, "Using openvr config file: %s\n", action_manifest_path);
- vr::VRInput()->GetActionHandle( "/actions/demo/in/HideCubes", &m_actionHideCubes );
- vr::VRInput()->GetActionHandle( "/actions/demo/in/HideThisController", &m_actionHideThisController);
- vr::VRInput()->GetActionHandle( "/actions/demo/in/TriggerHaptic", &m_actionTriggerHaptic );
- vr::VRInput()->GetActionHandle( "/actions/demo/in/AnalogInput", &m_actionAnalongInput );
-
- vr::VRInput()->GetActionSetHandle( "/actions/demo", &m_actionsetDemo );
+ if (!overlay_mode) {
+ vr::VRInput()->SetActionManifestPath(action_manifest_path);
+ vr::VRInput()->GetActionHandle( "/actions/demo/in/HideCubes", &m_actionHideCubes );
+ vr::VRInput()->GetActionSetHandle( "/actions/demo", &m_actionsetDemo );
+ }
return true;
}
@@ -876,8 +1185,6 @@ bool CMainApplication::BInitGL()
//-----------------------------------------------------------------------------
bool CMainApplication::BInitCompositor()
{
- vr::EVRInitError peError = vr::VRInitError_None;
-
if ( !vr::VRCompositor() )
{
printf( "Compositor initialization failed. See log file for details\n" );
@@ -887,6 +1194,56 @@ bool CMainApplication::BInitCompositor()
return true;
}
+//-----------------------------------------------------------------------------
+// Purpose: Create a VR overlay in which the window/video will be rendered.
+//-----------------------------------------------------------------------------
+bool CMainApplication::BInitOverlay()
+{
+ if ( !vr::VROverlay() )
+ {
+ printf( "Overlay initialization failed. See log file for details\n" );
+ return false;
+ }
+
+ vr::VROverlay()->CreateDashboardOverlay(
+ overlay_key,
+ mpv_file ? mpv_file : "vr-video-player",
+ &overlay_handle,
+ &thumbnail_handle
+ );
+
+ if (overlay_mouse_controls)
+ vr::VROverlay()->SetOverlayInputMethod(overlay_handle, vr::VROverlayInputMethod_Mouse);
+ else
+ vr::VROverlay()->SetOverlayInputMethod(overlay_handle, vr::VROverlayInputMethod_None);
+
+ vr::VROverlay()->SetOverlayFlag(overlay_handle, vr::VROverlayFlags_IgnoreTextureAlpha, true);
+ vr::VROverlay()->SetOverlayFlag(overlay_handle, vr::vrvid_VROverlayFlags_EnableControlBar, true);
+ vr::VROverlay()->SetOverlayFlag(overlay_handle, vr::vrvid_VROverlayFlags_EnableControlBarKeyboard, true);
+ vr::VROverlay()->SetOverlayFlag(overlay_handle, vr::vrvid_VROverlayFlags_EnableControlBarClose, true);
+ vr::VROverlay()->SetOverlayFlag(overlay_handle, vr::VROverlayFlags_WantsModalBehavior, false);
+ vr::VROverlay()->SetOverlayFlag(overlay_handle, vr::VROverlayFlags_SendVRDiscreteScrollEvents, true);
+ vr::VROverlay()->SetOverlayFlag(overlay_handle, vr::VROverlayFlags_VisibleInDashboard, true);
+
+ if (projection_mode == ProjectionMode::SPHERE360)
+ vr::VROverlay()->SetOverlayFlag(overlay_handle, vr::VROverlayFlags_Panorama, true);
+ else if (view_mode == ViewMode::LEFT_RIGHT)
+ vr::VROverlay()->SetOverlayFlag(overlay_handle, vr::VROverlayFlags_SideBySide_Parallel, true);
+ else if (view_mode == ViewMode::RIGHT_LEFT)
+ vr::VROverlay()->SetOverlayFlag(overlay_handle, vr::VROverlayFlags_SideBySide_Crossed, true);
+
+ if (projection_mode == ProjectionMode::FLAT && stretch)
+ vr::VROverlay()->SetOverlayTexelAspect(overlay_handle, 2.0);
+
+ vr::VROverlay()->SetOverlayWidthInMeters(overlay_handle, overlay_width);
+
+ overlay_xdo = xdo_new_with_opened_display(x_display, nullptr, 0);
+
+ overlay_icon_atom = XInternAtom(x_display, "_NET_WM_ICON", 0);
+
+ return true;
+}
+
//-----------------------------------------------------------------------------
// Purpose:
@@ -901,6 +1258,12 @@ void CMainApplication::Shutdown()
if( m_pContext )
{
+ if(mpv_thread.joinable())
+ mpv_thread.join();
+
+ if(mpv_file)
+ mpv.destroy();
+
if( m_bDebugOpenGL )
{
glDebugMessageControl( GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, nullptr, GL_FALSE );
@@ -930,6 +1293,12 @@ void CMainApplication::Shutdown()
glDeleteFramebuffers( 1, &rightEyeDesc.m_nRenderFramebufferId );
glDeleteTextures( 1, &rightEyeDesc.m_nResolveTextureId );
glDeleteFramebuffers( 1, &rightEyeDesc.m_nResolveFramebufferId );
+ /*
+ glDeleteRenderbuffers( 1, &mpvDesc.m_nDepthBufferId );
+ glDeleteTextures( 1, &mpvDesc.m_nRenderTextureId );
+ glDeleteFramebuffers( 1, &mpvDesc.m_nRenderFramebufferId );
+ glDeleteTextures( 1, &mpvDesc.m_nResolveTextureId );
+ glDeleteFramebuffers( 1, &mpvDesc.m_nResolveFramebufferId );*/
if( m_unCompanionWindowVAO != 0 )
{
@@ -939,10 +1308,6 @@ void CMainApplication::Shutdown()
{
glDeleteVertexArrays( 1, &m_unSceneVAO );
}
- if( m_unControllerVAO != 0 )
- {
- glDeleteVertexArrays( 1, &m_unControllerVAO );
- }
}
window_texture_deinit(&window_texture);
@@ -955,6 +1320,12 @@ void CMainApplication::Shutdown()
SDL_Quit();
+ if (overlay_xdo)
+ xdo_free(overlay_xdo);
+
+ if (overlay_buffers)
+ delete overlay_buffers;
+
if (x_display)
XCloseDisplay(x_display);
}
@@ -999,6 +1370,9 @@ bool CMainApplication::HandleInput()
SDL_Event sdlEvent;
bool bRet = false;
zoom_resize = false;
+ int64_t video_width = 0;
+ int64_t video_height = 0;
+ bool mpv_quit = false;
while ( SDL_PollEvent( &sdlEvent ) != 0 )
{
@@ -1024,6 +1398,42 @@ bool CMainApplication::HandleInput()
{
zoom_out();
}
+ if(mpv_file && sdlEvent.key.keysym.sym == SDLK_LEFT)
+ {
+ mpv.seek(-5.0); // Seek backwards 5 seconds
+ }
+ if(mpv_file && sdlEvent.key.keysym.sym == SDLK_RIGHT)
+ {
+ mpv.seek(5.0); // Seek forwards 5 seconds
+ }
+ if(mpv_file && sdlEvent.key.keysym.sym == SDLK_SPACE)
+ {
+ mpv.toggle_pause();
+ }
+ }
+
+ bool opdoot = false;
+ if(mpv_file) {
+ int error = 0;
+ mpv.on_event(sdlEvent, &opdoot, &video_width, &video_height, &mpv_quit, &error);
+ if(mpv_quit && error != 0)
+ exit_code = 2;
+ }
+
+ if(opdoot)
+ set_render_update();
+
+ if(mpv_quit)
+ bRet = true;
+
+ // TODO: Allow video resize to update texture size
+ if(video_width > 0 && video_height > 0 && video_width != mpv_video_width && video_height != mpv_video_height && !mpv_video_loaded && !mpv_loaded_in_thread) {
+ mpv_video_width = video_width;
+ mpv_video_height = video_height;
+ pixmap_texture_width = mpv_video_width;
+ pixmap_texture_height = mpv_video_height;
+ mpv_video_loaded = true;
+ SetupScene();
}
}
@@ -1103,6 +1513,14 @@ bool CMainApplication::HandleInput()
window_resize_time = SDL_GetTicks();
window_resized = false;
+ if (overlay_mode) {
+ vr::HmdVector2_t scale = {(float)window_width, (float)window_height};
+ vr::VROverlay()->SetOverlayMouseScale(overlay_handle, &scale);
+
+ UpdateOverlayTitle();
+ UpdateOverlayIcon();
+ }
+
if(focused_window_changed) {
XSelectInput(x_display, src_window_id, StructureNotifyMask|VisibilityChangeMask|KeyPressMask|KeyReleaseMask);
XFixesSelectCursorInput(x_display, src_window_id, XFixesDisplayCursorNotifyMask);
@@ -1118,6 +1536,18 @@ bool CMainApplication::HandleInput()
glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&window_texture));
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &pixmap_texture_width);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &pixmap_texture_height);
+ if(pixmap_texture_width == 0)
+ pixmap_texture_width = 1;
+ if(pixmap_texture_height == 0)
+ pixmap_texture_height = 1;
+
+ if (overlay_mode) {
+ if (overlay_buffers) {
+ delete overlay_buffers;
+ overlay_buffers = nullptr;
+ }
+ overlay_buffers = new VideoBuffers(pixmap_texture_width, pixmap_texture_height);
+ }
glBindTexture(GL_TEXTURE_2D, 0);
SetupScene();
} else if(!window_resized && zoom_resize) {
@@ -1139,32 +1569,39 @@ bool CMainApplication::HandleInput()
ProcessVREvent( event );
}
- // Process SteamVR action state
- // UpdateActionState is called each frame to update the state of the actions themselves. The application
- // controls which action sets are active with the provided array of VRActiveActionSet_t structs.
- vr::VRActiveActionSet_t actionSet = { 0 };
- actionSet.ulActionSet = m_actionsetDemo;
- vr::VRInput()->UpdateActionState( &actionSet, sizeof(actionSet), 1 );
+ if (overlay_mode) {
+ while( vr::VROverlay()->PollNextOverlayEvent(
+ overlay_handle, &event, sizeof( event ) ) ) {
+ ProcessVREvent( event );
+ }
+ }
- if(GetDigitalActionState( m_actionHideCubes ) || m_bResetRotation) {
- printf("reset rotation!\n");
- //printf("pos, %f, %f, %f\n", m_mat4HMDPose[0][2], m_mat4HMDPose[1][2], m_mat4HMDPose[2][2]);
- // m_resetPos = m_mat4HMDPose;
- hmd_pos = current_pos;
- m_bResetRotation = false;
- m_reset_rotation = glm::inverse(hmd_rot);
+ if (!overlay_mode) {
+ // Process SteamVR action state
+ // UpdateActionState is called each frame to update the state of
+ // the actions themselves. The application controls which action
+ // sets are active with the provided array of
+ // VRActiveActionSet_t structs.
+ vr::VRActiveActionSet_t actionSet = {0};
+ actionSet.ulActionSet = m_actionsetDemo;
+ vr::VRInput()->UpdateActionState(&actionSet, sizeof(actionSet),
+ 1);
+
+ if (GetDigitalActionState(m_actionHideCubes) ||
+ m_bResetRotation) {
+ printf("reset rotation!\n");
+ // printf("pos, %f, %f, %f\n", m_mat4HMDPose[0][2],
+ // m_mat4HMDPose[1][2], m_mat4HMDPose[2][2]);
+ // m_resetPos = m_mat4HMDPose;
+ hmd_pos = current_pos;
+ m_bResetRotation = false;
+ m_reset_rotation = glm::inverse(hmd_rot);
+ }
}
if(!free_camera)
hmd_pos = current_pos;
- vr::InputAnalogActionData_t analogData;
- if ( vr::VRInput()->GetAnalogActionData( m_actionAnalongInput, &analogData, sizeof( analogData ), vr::k_ulInvalidInputValueHandle ) == vr::VRInputError_None && analogData.bActive )
- {
- m_vAnalogValue[0] = analogData.x;
- m_vAnalogValue[1] = analogData.y;
- }
-
return bRet;
}
@@ -1173,8 +1610,6 @@ bool CMainApplication::HandleInput()
//-----------------------------------------------------------------------------
void CMainApplication::RunMainLoop()
{
- bool bQuit = false;
-
SDL_StartTextInput();
SDL_Joystick *controller = SDL_JoystickOpen(0);
@@ -1184,11 +1619,26 @@ void CMainApplication::RunMainLoop()
while ( !bQuit )
{
+ set_current_context(m_pContext);
bQuit = HandleInput();
+ if(bQuitSignal)
+ bQuit = true;
+
+ if(bQuit) {
+ running = false;
+ set_render_update();
+ }
+
RenderFrame();
+ set_current_context(NULL);
}
+ if(mpv_thread.joinable())
+ mpv_thread.join();
+
+ set_current_context(m_pContext);
+
if (controller)
SDL_JoystickClose(controller);
@@ -1213,6 +1663,68 @@ void CMainApplication::ProcessVREvent( const vr::VREvent_t & event )
dprintf( "Device %u updated.\n", event.trackedDeviceIndex );
}
break;
+
+ case vr::VREvent_OverlayClosed:
+ {
+ bQuitSignal = true;
+ }
+ break;
+
+ case vr::VREvent_KeyboardCharInput:
+ if (overlay_xdo && src_window_id != None) {
+ char text[sizeof(event.data.keyboard.cNewInput) + 1] = {0};
+ memcpy(text, event.data.keyboard.cNewInput,
+ sizeof(event.data.keyboard.cNewInput));
+ xdo_enter_text_window(overlay_xdo, src_window_id, text,
+ 12 * 1000);
+ }
+ break;
+
+ case vr::VREvent_MouseMove:
+ if (overlay_xdo && src_window_id != None) {
+ xdo_move_mouse_relative_to_window(
+ overlay_xdo, src_window_id,
+ event.data.mouse.x,
+ event.data.mouse.y
+ );
+ }
+ break;
+
+ case vr::VREvent_MouseButtonUp:
+ if (overlay_xdo && src_window_id != None) {
+ xdo_mouse_up(overlay_xdo, src_window_id,
+ event.data.mouse.button);
+ }
+ break;
+
+ case vr::VREvent_MouseButtonDown:
+ if (overlay_xdo && (src_window_id != None || mpv_file)) {
+ if (mpv_file)
+ mpv.toggle_pause();
+ else
+ xdo_mouse_down(overlay_xdo, src_window_id,
+ event.data.mouse.button);
+ }
+ break;
+
+ case vr::VREvent_ScrollDiscrete:
+ if (overlay_xdo && (src_window_id != None || mpv_file)) {
+ if (mpv_file)
+ {
+ if (event.data.scroll.ydelta > 0)
+ mpv.seek(-5.0);
+ else if (event.data.scroll.ydelta < 0)
+ mpv.seek(5.0);
+ }
+ else
+ {
+ if (event.data.scroll.ydelta > 0)
+ xdo_click_window(overlay_xdo, src_window_id, 4);
+ else if (event.data.scroll.ydelta < 0)
+ xdo_click_window(overlay_xdo, src_window_id, 5);
+ }
+ }
+ break;
}
}
@@ -1222,16 +1734,26 @@ void CMainApplication::ProcessVREvent( const vr::VREvent_t & event )
//-----------------------------------------------------------------------------
void CMainApplication::RenderFrame()
{
+ if(mpvBuffers != nullptr)
+ {
+ mpvBuffers->swap_mutex.lock();
+ }
+
// for now as fast as possible
if ( m_pHMD )
{
- RenderStereoTargets();
- RenderCompanionWindow();
-
- vr::Texture_t leftEyeTexture = {(void*)(uintptr_t)leftEyeDesc.m_nResolveTextureId, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
- vr::VRCompositor()->Submit(vr::Eye_Left, &leftEyeTexture );
- vr::Texture_t rightEyeTexture = {(void*)(uintptr_t)rightEyeDesc.m_nResolveTextureId, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
- vr::VRCompositor()->Submit(vr::Eye_Right, &rightEyeTexture );
+ if (overlay_mode) {
+ RenderOverlay();
+ }
+ else {
+ RenderStereoTargets();
+ RenderCompanionWindow();
+
+ vr::Texture_t leftEyeTexture = {(void*)(uintptr_t)leftEyeDesc.m_nResolveTextureId, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
+ vr::VRCompositor()->Submit(vr::Eye_Left, &leftEyeTexture );
+ vr::Texture_t rightEyeTexture = {(void*)(uintptr_t)rightEyeDesc.m_nResolveTextureId, vr::TextureType_OpenGL, vr::ColorSpace_Gamma };
+ vr::VRCompositor()->Submit(vr::Eye_Right, &rightEyeTexture );
+ }
}
if ( m_bVblank && m_bGlFinishHack )
@@ -1244,7 +1766,7 @@ void CMainApplication::RenderFrame()
}
// SwapWindow
- {
+ if (!overlay_mode) {
SDL_GL_SwapWindow( m_pCompanionWindow );
}
@@ -1263,6 +1785,11 @@ void CMainApplication::RenderFrame()
glFinish();
}
+ if(mpvBuffers != nullptr)
+ {
+ mpvBuffers->swap_mutex.unlock();
+ }
+
// Spew out the controller and pose count whenever they change.
if ( m_iTrackedControllerCount != m_iTrackedControllerCount_Last || m_iValidPoseCount != m_iValidPoseCount_Last )
{
@@ -1272,7 +1799,10 @@ void CMainApplication::RenderFrame()
dprintf( "PoseCount:%d(%s) Controllers:%d\n", m_iValidPoseCount, m_strPoseClasses.c_str(), m_iTrackedControllerCount );
}
- UpdateHMDMatrixPose();
+ if (!overlay_mode)
+ UpdateHMDMatrixPose();
+ else
+ vr::VROverlay()->WaitFrameSync(20);
}
//-----------------------------------------------------------------------------
@@ -1503,8 +2033,37 @@ bool CMainApplication::CreateAllShaders()
"}\n"
);
+ m_unOverlayProgramID = CompileGLShader(
+ "OverlayProgram",
+
+ // vertex shader
+ "#version 410 core\n"
+ "layout(location = 0) in vec4 position;\n"
+ "layout(location = 1) in vec2 v2UVIn;\n"
+ "noperspective out vec2 v2UV;\n"
+ "void main()\n"
+ "{\n"
+ " v2UV = vec2(v2UVIn.x, 1.0 - v2UVIn.y);\n"
+ " gl_Position = vec4(2.0 * position.x + 1.0, position.yzw);\n"
+ "}\n",
+
+ // fragment shader
+ "#version 410 core\n"
+ "uniform sampler2D mytexture;\n"
+ "noperspective in vec2 v2UV;\n"
+ "out vec4 outputColor;\n"
+ "void main()\n"
+ "{\n"
+ " vec4 col = texture(mytexture, v2UV);\n"
+ " outputColor = col.rgba;\n"
+ "}\n"
+ );
+
+ m_unOverlayTextureLoc = glGetUniformLocation(m_unOverlayProgramID, "mytexture");
+
return m_unSceneProgramID != 0
- && m_unCompanionWindowProgramID != 0;
+ && m_unCompanionWindowProgramID != 0 &&
+ m_unOverlayProgramID != 0;
}
bool CMainApplication::SetCursorFromX11CursorImage(XFixesCursorImage *x11_cursor_image) {
@@ -1541,7 +2100,7 @@ bool CMainApplication::SetCursorFromX11CursorImage(XFixesCursorImage *x11_cursor
}
}
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, arrow_image_width, arrow_image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, cursor_data);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, arrow_image_width, arrow_image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, cursor_data);
delete []cursor_data;
glGenerateMipmap(GL_TEXTURE_2D);
@@ -1580,6 +2139,52 @@ Window CMainApplication::get_focused_window() {
return None;
}
+void CMainApplication::save_config() {
+ if(free_camera) {
+ switch(projection_mode) {
+ case ProjectionMode::SPHERE: {
+ config.sphere.position = hmd_pos;
+ break;
+ }
+ case ProjectionMode::FLAT: {
+ config.flat.position = hmd_pos;
+ break;
+ }
+ case ProjectionMode::CYLINDER: {
+ config.plane.position = hmd_pos;
+ break;
+ }
+ case ProjectionMode::SPHERE360: {
+ config.sphere360.position = hmd_pos;
+ break;
+ }
+ }
+ }
+ switch(projection_mode) {
+ case ProjectionMode::SPHERE: {
+ config.sphere.rotation = m_reset_rotation;
+ config.sphere.zoom = zoom;
+ break;
+ }
+ case ProjectionMode::FLAT: {
+ config.flat.rotation = m_reset_rotation;
+ config.flat.zoom = zoom;
+ break;
+ }
+ case ProjectionMode::CYLINDER: {
+ config.plane.rotation = m_reset_rotation;
+ config.plane.zoom = zoom;
+ break;
+ }
+ case ProjectionMode::SPHERE360: {
+ config.sphere360.rotation = m_reset_rotation;
+ config.sphere360.zoom = zoom;
+ break;
+ }
+ }
+ ::save_config(config);
+}
+
//-----------------------------------------------------------------------------
// Purpose: create a sea of cubes
@@ -1911,7 +2516,8 @@ void CMainApplication::AddCubeToScene( const glm::mat4 &mat, std::vector<float>
if(stretch)
arrow_ratio = width_ratio * 2.0;
} else if (projection_mode == ProjectionMode::SPHERE360) {
- border_width_return += 2; // Meh, hac k to deal with seams a bit
+ if(!mpv_file)
+ border_width_return += 2; // Meh, hac k to deal with seams a bit
double px = (double)border_width_return / (double)pixmap_texture_width;
double py = (double)border_width_return / (double)pixmap_texture_height;
@@ -2006,6 +2612,27 @@ bool CMainApplication::CreateFrameBuffer( int nWidth, int nHeight, FramebufferDe
return true;
}
+void CMainApplication::set_current_context(SDL_GLContext context) {
+ std::lock_guard<std::mutex> lock(context_mutex);
+ SDL_GL_MakeCurrent(m_pCompanionWindow, context);
+}
+
+bool CMainApplication::take_render_update() {
+ std::unique_lock<std::mutex> lock(mpv_render_update_mutex);
+ while(!mpv_render_update && running)
+ {
+ mpv_render_update_condition.wait(lock);
+ }
+ mpv_render_update = false;
+ return true;
+}
+
+void CMainApplication::set_render_update() {
+ std::lock_guard<std::mutex> lock(mpv_render_update_mutex);
+ mpv_render_update = true;
+ mpv_render_update_condition.notify_one();
+}
+
//-----------------------------------------------------------------------------
// Purpose:
@@ -2081,6 +2708,7 @@ void CMainApplication::SetupCompanionWindow()
//-----------------------------------------------------------------------------
void CMainApplication::RenderStereoTargets()
{
+
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
glEnable( GL_MULTISAMPLE );
@@ -2101,7 +2729,7 @@ void CMainApplication::RenderStereoTargets()
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0 );
-
+
glEnable( GL_MULTISAMPLE );
// Right Eye
@@ -2122,6 +2750,8 @@ void CMainApplication::RenderStereoTargets()
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0 );
+ //glEnable( GL_MULTISAMPLE );
+
//glBindTexture(GL_TEXTURE_2D, 0);
}
@@ -2131,7 +2761,7 @@ void CMainApplication::RenderStereoTargets()
//-----------------------------------------------------------------------------
void CMainApplication::RenderScene( vr::Hmd_Eye nEye )
{
- if(!src_window_id)
+ if(!src_window_id && !mpv_file)
return;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@@ -2193,45 +2823,29 @@ void CMainApplication::RenderScene( vr::Hmd_Eye nEye )
m[0] += (-cursor_offset_x * arrow_drawn_scale_x) / (float)window_width;
m[1] += (-cursor_offset_y * arrow_drawn_scale_y) / (float)window_height;
- glUniform2fv(m_nCursorLocation, 1, &m[0]);
-
glBindVertexArray( m_unSceneVAO );
glActiveTexture(GL_TEXTURE0);
- glBindTexture( GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&window_texture) );
- glActiveTexture(GL_TEXTURE1);
- glBindTexture(GL_TEXTURE_2D, arrow_image_texture_id);
- glDrawArrays( GL_TRIANGLES, 0, m_uiVertcount );
- glBindVertexArray( 0 );
-
- glActiveTexture(GL_TEXTURE0);
-#if 0
- bool bIsInputAvailable = m_pHMD->IsInputAvailable();
-
- if( bIsInputAvailable )
+ if(mpv_file)
{
- // draw the controller axis lines
- glUseProgram( m_unControllerTransformProgramID );
- glUniformMatrix4fv( m_nControllerMatrixLocation, 1, GL_FALSE, glm::value_ptr(GetCurrentViewProjectionMatrix( nEye )));
- glBindVertexArray( m_unControllerVAO );
- glDrawArrays( GL_LINES, 0, m_uiControllerVertcount );
- glBindVertexArray( 0 );
+ if(mpvBuffers != nullptr)
+ {
+ m[0] = -1.0f;
+ m[1] = -1.0f;
+ glBindTexture(GL_TEXTURE_2D, mpvBuffers->get_showTextureId());
+ }
}
-
- // ----- Render Model rendering -----
- glUseProgram( m_unRenderModelProgramID );
-
- for ( EHand eHand = Left; eHand <= Right; ((int&)eHand)++ )
+ else
{
- if ( !m_rHand[eHand].m_bShowController || !m_rHand[eHand].m_pRenderModel )
- continue;
-
- const glm::mat4 & matDeviceToTracking = m_rHand[eHand].m_rmat4Pose;
- glm::mat4 matMVP = GetCurrentViewProjectionMatrix( nEye ) * matDeviceToTracking;
- glUniformMatrix4fv( m_nRenderModelMatrixLocation, 1, GL_FALSE, glm::value_ptr(matMVP));
-
- m_rHand[eHand].m_pRenderModel->Draw();
+ glBindTexture(GL_TEXTURE_2D, window_texture_get_opengl_texture_id(&window_texture));
}
-#endif
+ glUniform2fv(m_nCursorLocation, 1, &m[0]);
+ //glBindTexture(GL_TEXTURE_2D, mpv_file ? mpvDesc.m_nRenderTextureId : window_texture_get_opengl_texture_id(&window_texture));
+ glActiveTexture(GL_TEXTURE1);
+ glBindTexture(GL_TEXTURE_2D, mpv_file ? 0 : arrow_image_texture_id);
+ glDrawArrays( GL_TRIANGLES, 0, m_uiVertcount );
+
+ glBindVertexArray( 0 );
+ glActiveTexture(GL_TEXTURE0);
glUseProgram( 0 );
}
@@ -2267,6 +2881,188 @@ void CMainApplication::RenderCompanionWindow()
glUseProgram( 0 );
}
+//-----------------------------------------------------------------------------
+// Purpose: Present the window/video texture as an overlay
+//-----------------------------------------------------------------------------
+void CMainApplication::RenderOverlay() {
+ GLuint texture_id = 0;
+
+ if(mpv_file) {
+ if(!mpvBuffers)
+ return;
+ texture_id = mpvBuffers->get_showTextureId();
+ }
+ else if (overlay_buffers) {
+ // OpenVR relies on a shared OpenGL context
+ // which does not play well with the GLX
+ // extension used to copy data from the
+ // application, so data should be copied to a
+ // separate texture.
+
+ overlay_buffers->swap_buffer();
+
+ GLuint ref_texture = window_texture_get_opengl_texture_id(&window_texture);
+ texture_id = overlay_buffers->get_showTextureId();
+
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, ref_texture);
+ glBindVertexArray( m_unCompanionWindowVAO );
+ glUseProgram(m_unOverlayProgramID);
+ glUniform1i(m_unOverlayTextureLoc, 0);
+
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, overlay_buffers->get_renderFramebufferId());
+
+ glDisable(GL_DEPTH_TEST);
+ glDrawBuffer(GL_COLOR_ATTACHMENT0);
+ glViewport(0, 0, pixmap_texture_width, pixmap_texture_height);
+
+ glDrawElements( GL_TRIANGLES, m_uiCompanionWindowIndexSize/2, GL_UNSIGNED_SHORT, 0 );
+ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
+ }
+ else
+ return;
+
+ vr::Texture_t overlay_tex = {(void*)(uintptr_t)texture_id,
+ vr::TextureType_OpenGL, vr::ColorSpace_Gamma};
+
+ // Flip OpenGL texture upside down
+ vr::VRTextureBounds_t bounds = {0, 1, 1, 0};
+
+ vr::VROverlay()->SetOverlayTexture(overlay_handle, &overlay_tex);
+ vr::VROverlay()->SetOverlayTextureBounds(overlay_handle, &bounds);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Copy the title of the source X11 window as the overlay title
+//-----------------------------------------------------------------------------
+void CMainApplication::UpdateOverlayTitle() {
+ unsigned char *name = nullptr;
+ int name_len = 0;
+ int name_type = 0;
+ xdo_get_window_name(overlay_xdo, src_window_id,
+ &name, &name_len, &name_type);
+
+ if (!name) return;
+
+ std::string name_str;
+ name_str.resize(name_len);
+ for (int i = 0; i < name_len; i++)
+ name_str[i] = name[i];
+
+ vr::VROverlay()->SetOverlayName(overlay_handle, name_str.c_str());
+ XFree(name);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Update the overlay thumbnail icon to match the source window icon.
+//-----------------------------------------------------------------------------
+void CMainApplication::UpdateOverlayIcon() {
+ unsigned long best_offset = FindBestOverlayIcon();
+
+ if (best_offset == (unsigned long)-1)
+ return;
+
+ Atom type;
+ int format;
+ unsigned long nitems, bytes_after;
+ unsigned char *prop_data;
+
+ XGetWindowProperty(
+ x_display, src_window_id, overlay_icon_atom,
+ best_offset, 2, 0, AnyPropertyType, &type,
+ &format, &nitems, &bytes_after, &prop_data);
+
+ if (nitems != 2) {
+ XFree(prop_data);
+ return;
+ }
+
+ unsigned long width = ((unsigned long *)prop_data)[0];
+ unsigned long height = ((unsigned long *)prop_data)[1];
+
+ unsigned long n_expected = width * height;
+ unsigned char *icon;
+ XGetWindowProperty(
+ x_display, src_window_id,
+ overlay_icon_atom, best_offset + 2,
+ n_expected, 0, AnyPropertyType, &type,
+ &format, &nitems, &bytes_after, &icon);
+
+ if (nitems != n_expected) {
+ XFree(prop_data);
+ XFree(icon);
+ return;
+ }
+
+ std::vector<uint32_t> icon_data(n_expected);
+ for (size_t i = 0; i < n_expected; i++) {
+ icon_data[i] = ((unsigned long *)icon)[i] & 0xFFFFFFFFull;
+ uint32_t r = (icon_data[i] & 0x000000FF) >> 0;
+ uint32_t g = (icon_data[i] & 0x0000FF00) >> 8;
+ uint32_t b = (icon_data[i] & 0x00FF0000) >> 16;
+ uint32_t a = (icon_data[i] & 0xFF000000) >> 24;
+
+ icon_data[i] = b | (g << 8) | (r << 16) | (a << 24);
+ }
+
+ vr::VROverlay()->SetOverlayRaw(
+ thumbnail_handle, icon_data.data(),
+ width, height, sizeof(uint32_t));
+
+ XFree(icon);
+ XFree(prop_data);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Find the highest resolution icon that can be used as an overlay icon
+// and return its offset within _NET_WM_ICON.
+//
+// Will return -1 if no suitable icon is found.
+//-----------------------------------------------------------------------------
+unsigned long CMainApplication::FindBestOverlayIcon() {
+ unsigned long offset = 0;
+ unsigned long best_offset = (unsigned long)-1;
+ unsigned long best_size = 0;
+
+ Atom type;
+ int format;
+ unsigned long nitems, bytes_after;
+ unsigned char *prop_data;
+
+ while (true) {
+ XGetWindowProperty(
+ x_display, src_window_id, overlay_icon_atom,
+ offset, 2, 0, AnyPropertyType, &type,
+ &format, &nitems, &bytes_after, &prop_data);
+ if (nitems != 2) {
+ XFree(prop_data);
+ break;
+ }
+
+ unsigned long width = ((unsigned long *)prop_data)[0];
+ unsigned long height = ((unsigned long *)prop_data)[1];
+
+ unsigned long size = width * height;
+
+ // OpenVR docs say there's a limit to the amount
+ // of data that can be sent but no explicit
+ // limit is stated. When loading from a file,
+ // the icon size is limited to 1920x1080.
+ //
+ // Just setting an arbitrary for now. The
+ // highest resolution icon I found in my
+ // applications is 192x192
+ if (size > best_size && size <= 512 * 512) {
+ best_offset = offset;
+ best_size = size;
+ }
+
+ offset += 2 + size;
+ XFree(prop_data);
+ }
+
+ return best_offset;
+}
//-----------------------------------------------------------------------------
// Purpose: Gets a Matrix Projection Eye with respect to nEye.
@@ -2350,7 +3146,7 @@ void CMainApplication::UpdateHMDMatrixPose()
m_iValidPoseCount = 0;
m_strPoseClasses = "";
- for ( int nDevice = 0; nDevice < vr::k_unMaxTrackedDeviceCount; ++nDevice )
+ for ( int nDevice = 0; nDevice < (int)vr::k_unMaxTrackedDeviceCount; ++nDevice )
{
if ( m_rTrackedDevicePose[nDevice].bPoseIsValid )
{
@@ -2404,8 +3200,14 @@ CMainApplication *pMainApplication;
void reset_position(int signum)
{
- printf("ok\n");
- pMainApplication->ResetRotation();
+ write(STDOUT_FILENO, "ok\n", 3);
+ if(pMainApplication)
+ pMainApplication->ResetRotation();
+}
+
+void quit(int signum) {
+ if(pMainApplication)
+ pMainApplication->bQuitSignal = true;
}
//-----------------------------------------------------------------------------
@@ -2418,6 +3220,9 @@ int main(int argc, char *argv[])
signal(SIGUSR1, reset_position);
signal(SIGUSR2, reset_position);
+ signal(SIGINT, quit);
+ signal(SIGTERM, quit);
+
if (!pMainApplication->BInit())
{
pMainApplication->Shutdown();
@@ -2425,8 +3230,10 @@ int main(int argc, char *argv[])
}
pMainApplication->RunMainLoop();
-
pMainApplication->Shutdown();
- return 0;
+ if(pMainApplication->exit_code == 0)
+ pMainApplication->save_config();
+
+ return pMainApplication->exit_code;
}