//========= Copyright Valve Corporation ============// //Original BSD 3 License by Valve Corporation: //Copyright (c) 2015, Valve Corporation //All rights reserved. //Redistribution and use in source and binary forms, with or without modification, //are permitted provided that the following conditions are met: //1. Redistributions of source code must retain the above copyright notice, this //list of conditions and the following disclaimer. //2. Redistributions in binary form must reproduce the above copyright notice, //this list of conditions and the following disclaimer in the documentation and/or //other materials provided with the distribution. //3. Neither the name of the copyright holder 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND //ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED //WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE //DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 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. // Modified by: DEC05EBA #include #include "../include/window_texture.h" #include "../include/mpv.hpp" #include "../include/config.hpp" #include #include #include #define GLX_GLXEXT_PROTOTYPES #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef _countof #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 { 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 lock(swap_mutex); current_show_frame = (current_show_frame + 1) % BUFFER_DEPTH; } //----------------------------------------------------------------------------- // Purpose: //------------------------------------------------------------------------------ class CMainApplication { public: CMainApplication( int argc, char *argv[] ); virtual ~CMainApplication(); bool BInit(); bool BInitGL(); bool BInitCompositor(); bool BInitOverlay(); void Shutdown(); void RunMainLoop(); bool HandleInput(); void zoom_in(); void zoom_out(); void ProcessVREvent( const vr::VREvent_t & event ); void RenderFrame(); void ResetRotation(); void MoveCursor(float x, float y); void MouseButton(int button, bool down); void SetupScene(); void AddCubeToScene( const glm::mat4 &mat, std::vector &vertdata ); bool SetupStereoRenderTargets(); void SetupCompanionWindow(); void SetupCameras(); 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 ); glm::mat4 GetCurrentViewProjectionMatrix( vr::Hmd_Eye nEye ); void UpdateHMDMatrixPose(); glm::mat4 ConvertSteamVRMatrixToMatrix4( const vr::HmdMatrix34_t &matPose ); GLuint CompileGLShader( const char *pchShaderName, const char *pchVertexShader, const char *pchFragmentShader ); bool CreateAllShaders(); bool SetCursorFromX11CursorImage(XFixesCursorImage *x11_cursor_image); // 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_bVblank; bool m_bGlFinishHack; vr::IVRSystem *m_pHMD; vr::TrackedDevicePose_t m_rTrackedDevicePose[ vr::k_unMaxTrackedDeviceCount ]; glm::mat4 m_rmat4DevicePose[ vr::k_unMaxTrackedDeviceCount ]; private: // SDL bookkeeping SDL_Window *m_pCompanionWindow; uint32_t m_nCompanionWindowWidth; uint32_t m_nCompanionWindowHeight; SDL_GLContext m_pContext; SDL_GLContext m_pMpvContext; private: // OpenGL bookkeeping int m_iTrackedControllerCount; int m_iTrackedControllerCount_Last; int m_iValidPoseCount; int m_iValidPoseCount_Last; bool m_bResetRotation; 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 int m_iSceneVolumeWidth; int m_iSceneVolumeHeight; int m_iSceneVolumeDepth; float m_fScaleSpacing; float m_fScale; int m_iSceneVolumeInit; // if you want something other than the default 20x20x20 float m_fNearClip; float m_fFarClip; unsigned int m_uiVertcount; GLuint m_glSceneVertBuffer; GLuint m_unSceneVAO; GLuint m_unCompanionWindowVAO; GLuint m_glCompanionWindowIDVertBuffer; GLuint m_glCompanionWindowIDIndexBuffer; unsigned int m_uiCompanionWindowIndexSize; glm::mat4 m_mat4HMDPose; glm::mat4 m_mat4eyePosLeft; glm::mat4 m_mat4eyePosRight; glm::mat4 m_mat4ProjectionLeft; glm::mat4 m_mat4ProjectionRight; glm::vec3 current_pos = glm::vec3(0.0f, 0.0f, 0.0f); glm::vec3 hmd_pos = glm::vec3(0.0f, 0.0f, 0.0f); 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; glm::vec2 texCoord; }; struct VertexDataWindow { glm::vec2 position; glm::vec2 texCoord; VertexDataWindow( const glm::vec2 & pos, const glm::vec2 tex ) : position(pos), texCoord(tex) { } }; GLuint m_unSceneProgramID; GLuint m_unCompanionWindowProgramID; GLint m_nSceneMatrixLocation; GLint m_nSceneTextureOffsetXLocation; GLint m_nSceneTextureScaleXLocation; GLint m_nCursorLocation; GLint m_nArrowSizeLocation = -1; GLint m_myTextureLocation = -1; GLint m_arrowTextureLocation = -1; struct FramebufferDesc { GLuint m_nDepthBufferId; GLuint m_nRenderTextureId; GLuint m_nRenderFramebufferId; GLuint m_nResolveTextureId; GLuint m_nResolveFramebufferId; }; 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::VRActionSetHandle_t m_actionsetDemo = vr::k_ulInvalidActionSetHandle; private: // X compositor Display *x_display = nullptr; Atom net_active_window_atom; Window src_window_id = None; WindowTexture window_texture; bool follow_focused = false; 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; 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; bool zoom_resize = false; int x_fixes_event_base; int x_fixes_error_base; int prev_visibility_state = VisibilityFullyObscured; GLint pixmap_texture_width = 1; GLint pixmap_texture_height = 1; ProjectionMode projection_mode = ProjectionMode::SPHERE; double zoom = 0.0; float cursor_scale = 2.0f; ViewMode view_mode = ViewMode::LEFT_RIGHT; bool stretch = true; 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; int arrow_image_width = 1; int arrow_image_height = 1; int cursor_offset_x = 0; int cursor_offset_y = 0; 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; }; //--------------------------------------------------------------------------------------------------------------------- // Purpose: Returns true if the action is active and had a rising edge //--------------------------------------------------------------------------------------------------------------------- bool GetDigitalActionRisingEdge(vr::VRActionHandle_t action, vr::VRInputValueHandle_t *pDevicePath = nullptr ) { vr::InputDigitalActionData_t actionData; vr::VRInput()->GetDigitalActionData(action, &actionData, sizeof(actionData), vr::k_ulInvalidInputValueHandle ); if (pDevicePath) { *pDevicePath = vr::k_ulInvalidInputValueHandle; if (actionData.bActive) { vr::InputOriginInfo_t originInfo; if (vr::VRInputError_None == vr::VRInput()->GetOriginTrackedDeviceInfo(actionData.activeOrigin, &originInfo, sizeof(originInfo))) { *pDevicePath = originInfo.devicePath; } } } return actionData.bActive && actionData.bChanged && actionData.bState; } //--------------------------------------------------------------------------------------------------------------------- // Purpose: Returns true if the action is active and had a falling edge //--------------------------------------------------------------------------------------------------------------------- bool GetDigitalActionFallingEdge(vr::VRActionHandle_t action, vr::VRInputValueHandle_t *pDevicePath = nullptr ) { vr::InputDigitalActionData_t actionData; vr::VRInput()->GetDigitalActionData(action, &actionData, sizeof(actionData), vr::k_ulInvalidInputValueHandle ); if (pDevicePath) { *pDevicePath = vr::k_ulInvalidInputValueHandle; if (actionData.bActive) { vr::InputOriginInfo_t originInfo; if (vr::VRInputError_None == vr::VRInput()->GetOriginTrackedDeviceInfo(actionData.activeOrigin, &originInfo, sizeof(originInfo))) { *pDevicePath = originInfo.devicePath; } } } return actionData.bActive && actionData.bChanged && !actionData.bState; } //--------------------------------------------------------------------------------------------------------------------- // Purpose: Returns true if the action is active and its state is true //--------------------------------------------------------------------------------------------------------------------- bool GetDigitalActionState(vr::VRActionHandle_t action, vr::VRInputValueHandle_t *pDevicePath = nullptr ) { vr::InputDigitalActionData_t actionData; vr::VRInput()->GetDigitalActionData(action, &actionData, sizeof(actionData), vr::k_ulInvalidInputValueHandle ); if (pDevicePath) { *pDevicePath = vr::k_ulInvalidInputValueHandle; if (actionData.bActive) { vr::InputOriginInfo_t originInfo; if (vr::VRInputError_None == vr::VRInput()->GetOriginTrackedDeviceInfo(actionData.activeOrigin, &originInfo, sizeof(originInfo))) { *pDevicePath = originInfo.devicePath; } } } return actionData.bActive && actionData.bState; } //----------------------------------------------------------------------------- // Purpose: Outputs a set of optional arguments to debugging output, using // the printf format setting specified in fmt*. //----------------------------------------------------------------------------- void dprintf( const char *fmt, ... ) { va_list args; char buffer[ 2048 ]; va_start( args, fmt ); vsnprintf( buffer, sizeof(buffer), fmt, args ); va_end( args ); if ( g_bPrintf ) printf( "%s", buffer ); } 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|] [--use-system-mpv-config] [--mpv-profile ] [--free-camera] [--reduce-flicker] [--overlay] [--overlay-key ] [--overlay-mouse|--no-overlay-mouse] [--overlay-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, " --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