diff options
-rw-r--r-- | README.md | 14 | ||||
-rw-r--r-- | TODO | 3 | ||||
-rwxr-xr-x | build.sh | 2 | ||||
-rw-r--r-- | include/mpv.hpp | 2 | ||||
-rw-r--r-- | project.conf | 6 | ||||
-rw-r--r-- | src/main.cpp | 513 | ||||
-rw-r--r-- | src/mpv.cpp | 7 |
7 files changed, 502 insertions, 45 deletions
@@ -3,12 +3,14 @@ A virtual reality video player for Linux, based on Valve's openvr `hellovr_openg Currently only works with stereo video and equirectangular cube maps (youtube 360 videos) when used for vr videos, but if the vr video player is launched with the `--plane` option then you can view the video as a regular video in vr without depth (like a cinema). +You can view videos directly in the VR headset or as an overlay in the SteamVR menu if you use the --overlay option. + ## Note -Might now work when using a compositor such as picom when using the glx backend (when capturing a window). +Might not work when using a compositor such as picom when using the glx backend (when capturing a window). # Building -Run `./build.sh` or if you are running Arch Linux, then you can find it on aur under the name vr-video-player-git (`yay -S vr-video-player-git`).\ -Dependencies needed when building using `build.sh`: `glm, glew, sdl2, openvr, libx11, libxcomposite, libxfixes, libmpv`. +Run `./build.sh` or if you are running Arch Linux, then you can find it on aur under the name vr-video-player (`yay -S vr-video-player`).\ +Dependencies needed when building using `build.sh`: `glm, glew, sdl2, openvr, libx11, libxcomposite, libxfixes, libmpv, libxdo (xdotool)`. # How to use vr-video-player has two options. Either capture a window and view it in vr (works only on x11) or a work-in-progress built-in mpv option. @@ -62,6 +64,12 @@ Alternatively, you can run: ``` and vr-video-player will automatically select the focused window (and update when the focused window changes). +To display a window as an overlay in SteamVR you can run: +``` +./vr-video-player --plane --overlay $(xdotool selectwindow) +``` +and click on the window you want to view in your VR headset. + # Input options The video might not be in front of you, so to move the video in front of you, you can do any of the following: * Pull the trigger on the vr controller @@ -7,3 +7,6 @@ Automatically use the right vr option when using mpv by looking at the file name Make stereo audio follow headset rotation in 180/360 mode (assume facing forward is the default audio mode). Fix sphere360... AAAAAA. Use egl instead of glx for xcomposite window capture because nvidia glx has a limitation where only 1 pixmap can be created for a window which means that with some compositors (gnome for example with CSD or picom in GLX mode) it will fail to create a texture and the captured texture will instead be black. Nvidia with egl doesn't have this limitation. +Add option to capture monitors. This would be useful specifically with the --overlay option. +Allow selecting window/monitor in overlay. +Use lanczos resampling shader for improved texture quality. @@ -1,6 +1,6 @@ #!/bin/sh -e -dependencies="glm glew sdl2 openvr x11 xcomposite xfixes mpv" +dependencies="glew sdl2 openvr x11 xcomposite xfixes mpv libxdo" includes=$(pkg-config --cflags $dependencies) libs="$(pkg-config --libs $dependencies) -lm -pthread" gcc -c src/window_texture.c -O2 -DNDEBUG $includes diff --git a/include/mpv.hpp b/include/mpv.hpp index 09ba2c1..29817c8 100644 --- a/include/mpv.hpp +++ b/include/mpv.hpp @@ -11,7 +11,7 @@ public: Mpv() = default; ~Mpv(); - bool create(bool use_system_mpv_config); + bool create(bool use_system_mpv_config, const char *profile = "gpu-hq"); bool destroy(); bool load_file(const char *path); diff --git a/project.conf b/project.conf index 701b959..70874a7 100644 --- a/project.conf +++ b/project.conf @@ -5,11 +5,11 @@ version = "0.1.0" platforms = ["linux"] [dependencies] -glm = "0" glew = "2" sdl2 = "2" -openvr = "1" +openvr = ">=1" x11 = "1" xcomposite = ">=0.2" xfixes = ">=5" -mpv = ">=1"
\ No newline at end of file +mpv = ">=1" +libxdo = ">=2" diff --git a/src/main.cpp b/src/main.cpp index 501e43a..d29357d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -49,6 +49,8 @@ #include <X11/Xlib.h> #include <X11/extensions/Xfixes.h> +#include <xdo.h> + #include <stdio.h> #include <string> #include <cstdlib> @@ -70,6 +72,14 @@ #define _countof(x) (sizeof(x)/sizeof((x)[0])) #endif +// Not in public headers in older version of openvr. +namespace vr +{ + 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 { @@ -180,6 +190,7 @@ public: bool BInit(); bool BInitGL(); bool BInitCompositor(); + bool BInitOverlay(); void Shutdown(); @@ -204,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 ); @@ -344,6 +360,7 @@ private: // X compositor bool focused_window_changed = true; bool focused_window_set = false; 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; @@ -395,6 +412,18 @@ private: // X compositor 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; }; @@ -484,7 +513,7 @@ 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|--video video|<window_id>] [--use-system-mpv-config] [--free-camera] [--reduce-flicker]\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] [--no-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"); @@ -500,10 +529,17 @@ static void usage() { 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, " --free-camera If this option is set, then the camera wont follow your position. This option is enabled unless --sphere or --sphere360 options are used\n"); + fprintf(stderr, " --no-free-camera If this option is set, then the camera will follow your position. This option is enabled when --sphere or --sphere360 options are used\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"); @@ -580,6 +616,7 @@ CMainApplication::CMainApplication( int argc, char *argv[] ) bool zoom_set = false; bool cursor_scale_set = false; bool cursor_wrap_set = false; + bool free_camera_set = false; memset(&window_texture, 0, sizeof(window_texture)); @@ -679,11 +716,31 @@ CMainApplication::CMainApplication( int argc, char *argv[] ) ++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; + free_camera_set = true; + } else if(strcmp(argv[i], "--no-free-camera") == 0) { + free_camera = false; + free_camera_set = 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 { @@ -707,6 +764,14 @@ CMainApplication::CMainApplication( int argc, char *argv[] ) usage(); } + if(!free_camera_set) { + if(overlay_mode || projection_mode == ProjectionMode::SPHERE || projection_mode == ProjectionMode::SPHERE360) { + free_camera = false; + } else { + free_camera = true; + } + } + if(!zoom_set && projection_mode != ProjectionMode::SPHERE) { zoom = 1.0; } @@ -857,7 +922,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 ) { @@ -874,7 +941,11 @@ bool CMainApplication::BInit() 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 ); @@ -955,7 +1026,13 @@ 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; @@ -964,12 +1041,11 @@ bool CMainApplication::BInit() if(mpv_file) { mpv_thread = std::thread([&]{ set_current_context(m_pMpvContext); - if(!mpv.create(use_system_mpv_config)) + if(!mpv.create(use_system_mpv_config, mpv_profile)) return; mpv.load_file(mpv_file); set_current_context(NULL); - while(running) { @@ -1044,9 +1120,11 @@ bool CMainApplication::BInit() fprintf(stderr, "Using openvr config file: %s\n", action_manifest_path); - vr::VRInput()->SetActionManifestPath(action_manifest_path); - vr::VRInput()->GetActionHandle( "/actions/demo/in/HideCubes", &m_actionHideCubes ); - 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; } @@ -1130,6 +1208,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: @@ -1206,6 +1334,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); } @@ -1393,6 +1527,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); @@ -1412,6 +1554,14 @@ bool CMainApplication::HandleInput() 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) { @@ -1433,20 +1583,34 @@ 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) @@ -1513,6 +1677,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; } } @@ -1530,13 +1756,18 @@ void CMainApplication::RenderFrame() // 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 ) @@ -1549,7 +1780,7 @@ void CMainApplication::RenderFrame() } // SwapWindow - { + if (!overlay_mode) { SDL_GL_SwapWindow( m_pCompanionWindow ); } @@ -1582,7 +1813,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); } //----------------------------------------------------------------------------- @@ -1813,8 +2047,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) { @@ -2632,6 +2895,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. diff --git a/src/mpv.cpp b/src/mpv.cpp index 6403bdc..0683ab2 100644 --- a/src/mpv.cpp +++ b/src/mpv.cpp @@ -64,7 +64,7 @@ Mpv::~Mpv() { destroy(); } -bool Mpv::create(bool use_system_mpv_config) { +bool Mpv::create(bool use_system_mpv_config, const char *profile) { if(created) return false; @@ -103,10 +103,11 @@ bool Mpv::create(bool use_system_mpv_config) { }; //mpv_set_option_string(mpv, "vd-lavc-dr", "yes"); - mpv_set_option_string(mpv, "vo", "libmpv"); mpv_set_option_string(mpv, "hwdec", "auto"); - mpv_set_option_string(mpv, "profile", "gpu-hq"); + mpv_set_option_string(mpv, "profile", profile); mpv_set_option_string(mpv, "gpu-api", "opengl"); + // This has to be set after mpv_set_option_string(... "profile") since that option overwrites this + mpv_set_option_string(mpv, "vo", "libmpv"); if(mpv_render_context_create(&mpv_gl, mpv, params) < 0) { fprintf(stderr, "Error: mpv_render_context_create failed\n"); |