aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--README.md34
-rw-r--r--TODO12
-rwxr-xr-xbuild.sh11
-rw-r--r--include/config.hpp299
-rw-r--r--include/mpv.hpp31
-rw-r--r--project.conf7
-rw-r--r--src/main.cpp1125
-rw-r--r--src/mpv.cpp278
-rw-r--r--src/window_texture.c2
-rw-r--r--tests/main.cpp7
11 files changed, 1627 insertions, 180 deletions
diff --git a/.gitignore b/.gitignore
index 1f413f9..439cf44 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,5 +5,6 @@ tests/sibs-build/
tests/compile_commands.json
vr-video-player
window_texture.o
+mpv.o
main.o
.clangd/
diff --git a/README.md b/README.md
index 13e008c..47788b1 100644
--- a/README.md
+++ b/README.md
@@ -1,16 +1,31 @@
-A virtual reality video player for Linux running X11, based on Valve's openvr `hellovr_opengl` sample code: https://github.com/ValveSoftware/openvr/tree/master/samples
+A virtual reality video player for Linux, based on Valve's openvr `hellovr_opengl` sample code: https://github.com/ValveSoftware/openvr/tree/master/samples
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.
+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, libfixes`.
+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.
+# Using the built-in video player
+To play a video with the built-in mpv player, run vr-video-player like so:
+```
+./vr-video-player --sphere --video <file-path>
+```
+for example:
+```
+./vr-video-player --sphere --video /home/adam/Videos/my-cool-vr-video.mp4
+```
+`--sphere` can be replaced with `--flat`, `plane` or `--sphere360` for different display modes.
+
+# Capturing a window
Install xdotool and launch a video in a video player (I recommend mpv, because browsers, smplayer and vlc player remove the vr for 360 videos) and resize it to fit your monitor or larger for best quality and then,
if you want to watch 180 degree stereoscopic videos then run:
@@ -49,6 +64,13 @@ 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
* Press "Alt + F1"
@@ -56,7 +78,11 @@ The video might not be in front of you, so to move the video in front of you, yo
* Press the select/back button on an xbox controller while the application is focused
* Send a SIGUSR1 signal to the application, using the following command: `killall -USR1 vr-video-player`
-You can zoom the view with alt + Q/E.
+You can close the application by pressing Esc when the application is focused.
+
+You can zoom the view with Alt + Q/E.
+
+When using the built-in video player and the vr window is focused you can then use left/right arrow keys to move back/forward in the video and space to pause. In the future vr-video-player will show a graphical interface inside vr to manipulate the video.
You can launch vr-video-player without any arguments to show a list of all arguments.
diff --git a/TODO b/TODO
index e60dcdc..e0541b5 100644
--- a/TODO
+++ b/TODO
@@ -1 +1,11 @@
-Use libmpv. Make stereo audio follow headset rotation in 180/360 mode (assume facing forward is the default audio mode).
+Use pointer/button motion event instead of XQueryPointer every frame.
+Use directional audio when using mpv.
+Dynamically load mpv (with dlopen) when using the --video option instead of linking to it at compile time.
+Show mpv gui.
+Allow setting/changing video at runtime.
+Automatically use the right vr option when using mpv by looking at the file name (or file metadata?). There is a standard in filenames to specify the vr format.
+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.
diff --git a/build.sh b/build.sh
index 7bf6226..903bb40 100755
--- a/build.sh
+++ b/build.sh
@@ -1,8 +1,9 @@
#!/bin/sh -e
-dependencies="glm glew sdl2 openvr x11 xcomposite xfixes"
+dependencies="glew sdl2 openvr x11 xcomposite xfixes mpv libxdo"
includes=$(pkg-config --cflags $dependencies)
-libs=$(pkg-config --libs $dependencies)
-gcc -c src/window_texture.c -O2 $includes
-g++ -c src/main.cpp -O2 $includes
-g++ -o vr-video-player -O2 window_texture.o main.o -s $libs
+libs="$(pkg-config --libs $dependencies) -lm -pthread"
+gcc -c src/window_texture.c -O2 -DNDEBUG $includes
+g++ -c src/mpv.cpp -O2 -DNDEBUG $includes
+g++ -c src/main.cpp -O2 -DNDEBUG $includes
+g++ -o vr-video-player -O2 window_texture.o mpv.o main.o -s $libs
diff --git a/include/config.hpp b/include/config.hpp
new file mode 100644
index 0000000..fcf69bf
--- /dev/null
+++ b/include/config.hpp
@@ -0,0 +1,299 @@
+#pragma once
+
+#include <string>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <pwd.h>
+#include <sys/stat.h>
+#include <functional>
+#include <glm/glm.hpp>
+#include <glm/ext/quaternion_float.hpp>
+
+struct SphereConfig {
+ glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f);
+ glm::quat rotation = glm::quat(0.0f, 0.0f, 0.0f, 0.0f);
+ float zoom = 0.0f;
+};
+
+struct Sphere360Config {
+ glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f);
+ glm::quat rotation = glm::quat(0.0f, 0.0f, 0.0f, 0.0f);
+ float zoom = 0.0f;
+};
+
+struct FlatConfig {
+ glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f);
+ glm::quat rotation = glm::quat(0.0f, 0.0f, 0.0f, 0.0f);
+ float zoom = 0.0f;
+};
+
+struct PlaneConfig {
+ glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f);
+ glm::quat rotation = glm::quat(0.0f, 0.0f, 0.0f, 0.0f);
+ float zoom = 0.0f;
+};
+
+struct Config {
+ SphereConfig sphere;
+ Sphere360Config sphere360;
+ FlatConfig flat;
+ PlaneConfig plane;
+};
+
+static std::string get_home_dir() {
+ const char *home_dir = getenv("HOME");
+ if(!home_dir) {
+ passwd *pw = getpwuid(getuid());
+ home_dir = pw->pw_dir;
+ }
+
+ if(!home_dir) {
+ fprintf(stderr, "Error: Failed to get home directory of user, using /tmp directory\n");
+ home_dir = "/tmp";
+ }
+
+ return home_dir;
+}
+
+static std::string get_config_dir() {
+ std::string config_dir;
+ const char *xdg_config_home = getenv("XDG_CONFIG_HOME");
+ if(xdg_config_home) {
+ config_dir = xdg_config_home;
+ } else {
+ config_dir = get_home_dir() + "/.config";
+ }
+ config_dir += "/vr-video-player";
+ return config_dir;
+}
+
+static int create_directory_recursive(char *path) {
+ int path_len = strlen(path);
+ char *p = path;
+ char *end = path + path_len;
+ for(;;) {
+ char *slash_p = strchr(p, '/');
+
+ // Skips first '/', we don't want to try and create the root directory
+ if(slash_p == path) {
+ ++p;
+ continue;
+ }
+
+ if(!slash_p)
+ slash_p = end;
+
+ char prev_char = *slash_p;
+ *slash_p = '\0';
+ int err = mkdir(path, S_IRWXU);
+ *slash_p = prev_char;
+
+ if(err == -1 && errno != EEXIST)
+ return err;
+
+ if(slash_p == end)
+ break;
+ else
+ p = slash_p + 1;
+ }
+ return 0;
+}
+
+static bool file_get_content(const char *filepath, std::string &file_content) {
+ file_content.clear();
+ bool success = false;
+
+ FILE *file = fopen(filepath, "rb");
+ if(!file)
+ return success;
+
+ fseek(file, 0, SEEK_END);
+ long file_size = ftell(file);
+ if(file_size != -1) {
+ file_content.resize(file_size);
+ fseek(file, 0, SEEK_SET);
+ if((long)fread(&file_content[0], 1, file_size, file) == file_size)
+ success = true;
+ }
+
+ fclose(file);
+ return success;
+}
+
+struct StringView {
+ const char *str;
+ size_t size;
+
+ bool operator == (const char *other) const {
+ int len = strlen(other);
+ return (size_t)len == size && memcmp(str, other, size) == 0;
+ }
+
+ size_t find(char c) const {
+ const void *p = memchr(str, c, size);
+ if(!p)
+ return std::string::npos;
+ return (const char*)p - str;
+ }
+};
+
+using StringSplitCallback = std::function<bool(StringView line)>;
+
+static void string_split_char(const std::string &str, char delimiter, StringSplitCallback callback_func) {
+ size_t index = 0;
+ while(index < str.size()) {
+ size_t new_index = str.find(delimiter, index);
+ if(new_index == std::string::npos)
+ new_index = str.size();
+
+ if(!callback_func({str.data() + index, new_index - index}))
+ break;
+
+ index = new_index + 1;
+ }
+}
+
+static bool config_split_key_value(const StringView str, StringView &key, StringView &value) {
+ key.str = nullptr;
+ key.size = 0;
+
+ value.str = nullptr;
+ value.size = 0;
+
+ size_t index = str.find(' ');
+ if(index == std::string::npos)
+ return std::string::npos;
+
+ key.str = str.str;
+ key.size = index;
+
+ value.str = str.str + index + 1;
+ value.size = str.size - (index + 1);
+
+ return true;
+}
+
+static bool string_to_float(std::string str, float &value, const StringView key) {
+ value = 0.0;
+ errno = 0;
+ char *endptr;
+ value = strtod(str.c_str(), &endptr);
+ if(endptr == str.c_str() || errno != 0) {
+ fprintf(stderr, "Warning: Invalid config option %.*s is not a float\n", (int)key.size, key.str);
+ return false;
+ }
+ return true;
+}
+
+static bool string_to_vec3(std::string str, glm::vec3 &value, const StringView key) {
+ if(sscanf(str.data(), "%f|%f|%f", &value.x, &value.y, &value.z) != 3) {
+ fprintf(stderr, "Warning: Invalid config option %.*s is not a glm::vec3\n", (int)key.size, key.str);
+ return false;
+ }
+ return true;
+}
+
+static bool string_to_quat(std::string str, glm::quat &value, const StringView key) {
+ if(sscanf(str.data(), "%f|%f|%f|%f", &value.x, &value.y, &value.z, &value.w) != 4) {
+ fprintf(stderr, "Warning: Invalid config option %.*s is not a glm::quat\n", (int)key.size, key.str);
+ return false;
+ }
+ return true;
+}
+
+static Config read_config(bool &exists) {
+ setlocale(LC_ALL, "C");
+
+ Config config;
+
+ const std::string config_path = get_config_dir() + "/config";
+ std::string file_content;
+ if(!file_get_content(config_path.c_str(), file_content)) {
+ fprintf(stderr, "Warning: Failed to read config file: %s\n", config_path.c_str());
+ exists = false;
+ return config;
+ }
+
+ string_split_char(file_content, '\n', [&](StringView line) {
+ StringView key, value;
+ if(!config_split_key_value(line, key, value)) {
+ fprintf(stderr, "Warning: Invalid config option format: %.*s\n", (int)line.size, line.str);
+ return true;
+ }
+
+ if(key == "sphere.position") {
+ string_to_vec3(std::string(value.str, value.size), config.sphere.position, key);
+ } else if(key == "sphere.rotation") {
+ string_to_quat(std::string(value.str, value.size), config.sphere.rotation, key);
+ } else if(key == "sphere.zoom") {
+ string_to_float(std::string(value.str, value.size), config.sphere.zoom, key);
+ } else if(key == "sphere360.position") {
+ string_to_vec3(std::string(value.str, value.size), config.sphere360.position, key);
+ } else if(key == "sphere360.rotation") {
+ string_to_quat(std::string(value.str, value.size), config.sphere360.rotation, key);
+ } else if(key == "sphere360.zoom") {
+ string_to_float(std::string(value.str, value.size), config.sphere360.zoom, key);
+ } else if(key == "flat.position") {
+ string_to_vec3(std::string(value.str, value.size), config.flat.position, key);
+ } else if(key == "flat.rotation") {
+ string_to_quat(std::string(value.str, value.size), config.flat.rotation, key);
+ } else if(key == "flat.zoom") {
+ string_to_float(std::string(value.str, value.size), config.flat.zoom, key);
+ } else if(key == "plane.position") {
+ string_to_vec3(std::string(value.str, value.size), config.plane.position, key);
+ } else if(key == "plane.rotation") {
+ string_to_quat(std::string(value.str, value.size), config.plane.rotation, key);
+ } else if(key == "plane.zoom") {
+ string_to_float(std::string(value.str, value.size), config.plane.zoom, key);
+ } else {
+ fprintf(stderr, "Warning: Invalid config option: %.*s\n", (int)line.size, line.str);
+ }
+
+ return true;
+ });
+
+ exists = true;
+ return config;
+}
+
+static void save_config(const Config &config) {
+ setlocale(LC_ALL, "C");
+
+ const std::string config_path = get_config_dir() + "/config";
+
+ char dir_tmp[PATH_MAX];
+ strcpy(dir_tmp, config_path.c_str());
+ char *dir = dirname(dir_tmp);
+
+ if(create_directory_recursive(dir) != 0) {
+ fprintf(stderr, "Warning: Failed to create config directory: %s\n", dir);
+ return;
+ }
+
+ FILE *file = fopen(config_path.c_str(), "wb");
+ if(!file) {
+ fprintf(stderr, "Warning: Failed to create config file: %s\n", config_path.c_str());
+ return;
+ }
+
+ fprintf(file, "sphere.position %f|%f|%f\n", config.sphere.position.x, config.sphere.position.y, config.sphere.position.z);
+ fprintf(file, "sphere.rotation %f|%f|%f|%f\n", config.sphere.rotation.x, config.sphere.rotation.y, config.sphere.rotation.z, config.sphere.rotation.w);
+ fprintf(file, "sphere.zoom %f\n", config.sphere.zoom);
+
+ fprintf(file, "sphere360.position %f|%f|%f\n", config.sphere360.position.x, config.sphere360.position.y, config.sphere360.position.z);
+ fprintf(file, "sphere360.rotation %f|%f|%f|%f\n", config.sphere360.rotation.x, config.sphere360.rotation.y, config.sphere360.rotation.z, config.sphere360.rotation.w);
+ fprintf(file, "sphere360.zoom %f\n", config.sphere360.zoom);
+
+ fprintf(file, "flat.position %f|%f|%f\n", config.flat.position.x, config.flat.position.y, config.flat.position.z);
+ fprintf(file, "flat.rotation %f|%f|%f|%f\n", config.flat.rotation.x, config.flat.rotation.y, config.flat.rotation.z, config.flat.rotation.w);
+ fprintf(file, "flat.zoom %f\n", config.flat.zoom);
+
+ fprintf(file, "plane.position %f|%f|%f\n", config.plane.position.x, config.plane.position.y, config.plane.position.z);
+ fprintf(file, "plane.rotation %f|%f|%f|%f\n", config.plane.rotation.x, config.plane.rotation.y, config.plane.rotation.z, config.plane.rotation.w);
+ fprintf(file, "plane.zoom %f\n", config.plane.zoom);
+
+ fclose(file);
+} \ No newline at end of file
diff --git a/include/mpv.hpp b/include/mpv.hpp
new file mode 100644
index 0000000..29817c8
--- /dev/null
+++ b/include/mpv.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <stdint.h>
+#include <SDL.h>
+
+typedef struct mpv_handle mpv_handle;
+typedef struct mpv_render_context mpv_render_context;
+
+class Mpv {
+public:
+ Mpv() = default;
+ ~Mpv();
+
+ bool create(bool use_system_mpv_config, const char *profile = "gpu-hq");
+ bool destroy();
+
+ bool load_file(const char *path);
+ // |width| and |ħeight| are set to 0 unless there is an event to reconfigure video size
+ void on_event(SDL_Event &event, bool *render_update, int64_t *width, int64_t *height, bool *quit, int *error);
+ void seek(double seconds);
+ void toggle_pause();
+ void draw(unsigned int framebuffer_id, int width, int height);
+
+ bool created = false;
+ uint32_t wakeup_on_mpv_render_update = -1;
+ uint32_t wakeup_on_mpv_events = -1;
+
+ mpv_handle *mpv = nullptr;
+ mpv_render_context *mpv_gl = nullptr;
+ bool paused = false;
+}; \ No newline at end of file
diff --git a/project.conf b/project.conf
index 154a611..70874a7 100644
--- a/project.conf
+++ b/project.conf
@@ -5,10 +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" \ No newline at end of file
+xfixes = ">=5"
+mpv = ">=1"
+libxdo = ">=2"
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;
}
diff --git a/src/mpv.cpp b/src/mpv.cpp
new file mode 100644
index 0000000..0683ab2
--- /dev/null
+++ b/src/mpv.cpp
@@ -0,0 +1,278 @@
+#include "../include/mpv.hpp"
+#include <mpv/client.h>
+#include <mpv/render_gl.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <sys/wait.h>
+
+static bool exec_program_daemonized(const char **args) {
+ /* 1 argument */
+ if(args[0] == nullptr)
+ return false;
+
+ pid_t pid = vfork();
+ if(pid == -1) {
+ perror("Failed to vfork");
+ return false;
+ } else if(pid == 0) { /* child */
+ setsid();
+ signal(SIGHUP, SIG_IGN);
+
+ // Daemonize child to make the parent the init process which will reap the zombie child
+ pid_t second_child = vfork();
+ if(second_child == 0) { // child
+ execvp(args[0], (char* const*)args);
+ perror("execvp");
+ _exit(127);
+ } else if(second_child != -1) {
+ // TODO:
+ _exit(0);
+ }
+ } else { /* parent */
+ waitpid(pid, nullptr, 0);
+ }
+
+ return true;
+}
+
+static void show_notification(const char *title, const char *msg, const char *urgency) {
+ const char *args[] = { "notify-send", "-a", "vr-video-player", "-t", "10000", "-u", urgency, "--", title, msg, NULL };
+ exec_program_daemonized(args);
+}
+
+static void* get_proc_address_mpv(void*, const char *name) {
+ return SDL_GL_GetProcAddress(name);
+}
+
+static void on_mpv_events(void *ctx) {
+ Mpv *mpv = (Mpv*)ctx;
+ SDL_Event event;
+ event.type = mpv->wakeup_on_mpv_events;
+ SDL_PushEvent(&event);
+}
+
+static void on_mpv_render_update(void *ctx) {
+ Mpv *mpv = (Mpv*)ctx;
+ SDL_Event event;
+ event.type = mpv->wakeup_on_mpv_render_update;
+ SDL_PushEvent(&event);
+}
+
+Mpv::~Mpv() {
+ destroy();
+}
+
+bool Mpv::create(bool use_system_mpv_config, const char *profile) {
+ if(created)
+ return false;
+
+ mpv = mpv_create();
+ if(!mpv) {
+ fprintf(stderr, "Error: mpv_create failed\n");
+ return false;
+ }
+
+ if(use_system_mpv_config) {
+ mpv_set_option_string(mpv, "config", "yes");
+ mpv_set_option_string(mpv, "load-scripts", "yes");
+ }
+
+ if(mpv_initialize(mpv) < 0) {
+ fprintf(stderr, "Error: mpv_initialize failed\n");
+ mpv_destroy(mpv);
+ mpv = nullptr;
+ return false;
+ }
+
+ //mpv_request_log_messages(mpv, "debug");
+
+ mpv_opengl_init_params gl_init_params;
+ memset(&gl_init_params, 0, sizeof(gl_init_params));
+ gl_init_params.get_proc_address = get_proc_address_mpv;
+
+ int advanced_control = 1;
+
+ mpv_render_param params[] = {
+ { MPV_RENDER_PARAM_API_TYPE, (void*)MPV_RENDER_API_TYPE_OPENGL },
+ { MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init_params },
+ { MPV_RENDER_PARAM_ADVANCED_CONTROL, &advanced_control },
+ //{ MPV_RENDER_PARAM_BLOCK_FOR_TARGET_TIME, 0 }, // TODO: Manually sync after this
+ { MPV_RENDER_PARAM_INVALID, 0 }
+ };
+
+ //mpv_set_option_string(mpv, "vd-lavc-dr", "yes");
+ mpv_set_option_string(mpv, "hwdec", "auto");
+ 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");
+ mpv_destroy(mpv);
+ mpv = nullptr;
+ mpv_gl = nullptr;
+ return false;
+ }
+
+ wakeup_on_mpv_render_update = SDL_RegisterEvents(1);
+ wakeup_on_mpv_events = SDL_RegisterEvents(1);
+ if(wakeup_on_mpv_render_update == (uint32_t)-1 || wakeup_on_mpv_events == (uint32_t)-1) {
+ fprintf(stderr, "Error: SDL_RegisterEvents failed\n");
+ // TODO: Remove registered events?
+ wakeup_on_mpv_render_update = -1;
+ wakeup_on_mpv_events = -1;
+ mpv_render_context_free(mpv_gl);
+ mpv_destroy(mpv);
+ mpv = nullptr;
+ mpv_gl = nullptr;
+ return false;
+ }
+
+ mpv_set_wakeup_callback(mpv, on_mpv_events, this);
+ mpv_render_context_set_update_callback(mpv_gl, on_mpv_render_update, this);
+
+ created = true;
+ return true;
+}
+
+bool Mpv::destroy() {
+ if(!created)
+ return true;
+
+ if(mpv_gl)
+ mpv_render_context_free(mpv_gl);
+ if(mpv)
+ mpv_destroy(mpv);
+
+ created = false;
+ return true;
+}
+
+bool Mpv::load_file(const char *path) {
+ if(!created)
+ return false;
+
+ const char *cmd[] = { "loadfile", path, nullptr };
+ mpv_command_async(mpv, 0, cmd);
+ return true;
+}
+
+void Mpv::on_event(SDL_Event &event, bool *render_update, int64_t *width, int64_t *height, bool *quit, int *error) {
+ if(render_update)
+ *render_update = false;
+
+ if(width)
+ *width = 0;
+
+ if(height)
+ *height = 0;
+
+ if(quit)
+ *quit = false;
+
+ if(error)
+ *error = 0;
+
+ if(!created)
+ return;
+
+ if(event.type == wakeup_on_mpv_render_update) {
+ uint64_t flags = mpv_render_context_update(mpv_gl);
+ if(flags & MPV_RENDER_UPDATE_FRAME) {
+ if(render_update)
+ *render_update = true;
+ }
+ }
+
+ if(event.type == wakeup_on_mpv_events) {
+ while(true) {
+ mpv_event *mp_event = mpv_wait_event(mpv, 0);
+ if(mp_event->event_id == MPV_EVENT_NONE)
+ break;
+
+ if(mp_event->event_id == MPV_EVENT_LOG_MESSAGE) {
+ mpv_event_log_message *msg = (mpv_event_log_message*)mp_event->data;
+ //printf("log: %s", msg->text);
+ } else {
+ //printf("mpv event: %s\n", mpv_event_name(mp_event->event_id));
+ }
+
+ if(mp_event->event_id == MPV_EVENT_END_FILE) {
+ mpv_event_end_file *msg = (mpv_event_end_file*)mp_event->data;
+ if(msg->reason == MPV_END_FILE_REASON_ERROR) {
+ show_notification("vr video player mpv video error", mpv_error_string(msg->error), "critical");
+ if(quit) {
+ *quit = true;
+ *error = -1;
+ }
+ }
+ if(msg->reason == MPV_END_FILE_REASON_EOF) {
+ show_notification("vr video player", "the video ended", "low");
+ if(quit)
+ *quit = true;
+ }
+ }
+
+ if(mp_event->event_id == MPV_EVENT_VIDEO_RECONFIG) {
+ int64_t new_width = 0;
+ mpv_get_property(mpv, "width", MPV_FORMAT_INT64, &new_width);
+
+ int64_t new_height = 0;
+ mpv_get_property(mpv, "height", MPV_FORMAT_INT64, &new_height);
+
+ if(width)
+ *width = new_width;
+
+ if(height)
+ *height = new_height;
+ }
+ }
+ }
+}
+
+void Mpv::seek(double seconds) {
+ if(!created)
+ return;
+
+ char seconds_str[128];
+ snprintf(seconds_str, sizeof(seconds_str), "%f", seconds);
+
+ const char *cmd[] = { "seek", seconds_str, nullptr };
+ mpv_command_async(mpv, 0, cmd);
+}
+
+void Mpv::toggle_pause() {
+ if(!created)
+ return;
+
+ paused = !paused;
+ int pause_value = paused ? 1 : 0;
+ mpv_set_property_async(mpv, 0, "pause", MPV_FORMAT_FLAG, &pause_value);
+}
+
+void Mpv::draw(unsigned int framebuffer_id, int width, int height) {
+ if(!created)
+ return;
+
+ mpv_opengl_fbo fbo;
+ memset(&fbo, 0, sizeof(fbo));
+ fbo.fbo = framebuffer_id;
+ fbo.w = width;
+ fbo.h = height;
+
+ int flip_y = 0;
+ int shit = 1;
+
+ mpv_render_param params[] = {
+ { MPV_RENDER_PARAM_OPENGL_FBO, &fbo },
+ { MPV_RENDER_PARAM_FLIP_Y, &flip_y },
+ //{ MPV_RENDER_PARAM_SKIP_RENDERING, &shit },
+ { MPV_RENDER_PARAM_INVALID, 0 }
+ };
+
+ int res = mpv_render_context_render(mpv_gl, params);
+ //fprintf(stderr, "draw mpv: %d\n", res);
+} \ No newline at end of file
diff --git a/src/window_texture.c b/src/window_texture.c
index 0479254..54c598d 100644
--- a/src/window_texture.c
+++ b/src/window_texture.c
@@ -70,7 +70,7 @@ int window_texture_on_resize(WindowTexture *self) {
GLX_DRAWABLE_TYPE, GLX_PIXMAP_BIT | GLX_WINDOW_BIT,
GLX_BIND_TO_TEXTURE_TARGETS_EXT, GLX_TEXTURE_2D_BIT_EXT,
/*GLX_BIND_TO_MIPMAP_TEXTURE_EXT, True,*/
- GLX_BUFFER_SIZE, 24,
+ GLX_BUFFER_SIZE, 24,
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8,
diff --git a/tests/main.cpp b/tests/main.cpp
deleted file mode 100644
index 9ad80a6..0000000
--- a/tests/main.cpp
+++ /dev/null
@@ -1,7 +0,0 @@
-#include <stdio.h>
-
-int main(int argc, char **argv)
-{
- printf("hello, world!\n");
- return 0;
-}