From 936effa9db7dbf31fdaa7f465d1fb90a7bef85a6 Mon Sep 17 00:00:00 2001 From: milek7 Date: Tue, 29 Sep 2020 22:27:13 +0200 Subject: [PATCH] openvr WIP --- CMakeLists.txt | 12 ++ EU07.cpp | 2 +- Float3d.h | 2 +- Globals.cpp | 14 +- Globals.h | 4 +- Model3d.cpp | 38 ++++- Model3d.h | 7 +- Texture.cpp | 33 ++++- Texture.h | 6 + application.cpp | 1 + gl/framebuffer.cpp | 16 ++- headtrack.cpp | 4 +- material.cpp | 2 +- network/backend/asio.cpp | 4 +- renderer.cpp | 179 ++++++++++++++++++------ renderer.h | 23 ++- stdafx.h | 2 + vr/openvr_imp.cpp | 293 +++++++++++++++++++++++++++++++++++++++ vr/openvr_imp.h | 49 +++++++ vr/vr_interface.cpp | 31 +++++ vr/vr_interface.h | 36 +++++ 21 files changed, 693 insertions(+), 65 deletions(-) create mode 100644 vr/openvr_imp.cpp create mode 100644 vr/openvr_imp.h create mode 100644 vr/vr_interface.cpp create mode 100644 vr/vr_interface.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c798fc7f..e10c2aeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,6 +27,7 @@ file(GLOB HEADERS "*.h" "McZapkie/*.h" "gl/*.h" "network/*.h" +"vr/*.h" "network/backend/*.h" "widgets/*.h" "launcher/*.h" @@ -38,6 +39,7 @@ endif() option(USE_IMGUI_GL3 "Use OpenGL3+ imgui implementation" ON) option(WITH_UART "Compile with libserialport" ON) +option(WITH_OPENVR "Compile with OpenVR" ON) option(WITH_ZMQ "Compile with cppzmq" ON) option(USE_VSDEV_CAMERA "Use VS_Dev camera preview implementation" OFF) @@ -195,6 +197,12 @@ if (WITH_ZMQ) set(SOURCES ${SOURCES} "zmq_input.cpp") endif() +if (WITH_OPENVR) + add_definitions(-DWITH_OPENVR) + set(SOURCES ${SOURCES} "vr/openvr_imp.cpp" "vr/vr_interface.cpp") +endif() + + if (USE_VSDEV_CAMERA) add_definitions(-DUSE_VSDEV_CAMERA) set(SOURCES ${SOURCES} "extras/VS_Dev.cpp" "widgets/cameraview_vsdev.cpp") @@ -267,6 +275,10 @@ set_target_properties( ${PROJECT_NAME} DEBUG_POSTFIX "_d" ) +if (WITH_OPENVR) + target_link_libraries(${PROJECT_NAME} openvr_api) +endif() + find_package(GLFW3 REQUIRED) include_directories(${GLFW3_INCLUDE_DIR}) target_link_libraries(${PROJECT_NAME} ${GLFW3_LIBRARIES}) diff --git a/EU07.cpp b/EU07.cpp index f234d931..00b6f8fe 100644 --- a/EU07.cpp +++ b/EU07.cpp @@ -34,7 +34,7 @@ int main( int argc, char *argv[] ) auto result { Application.init( argc, argv ) }; if( result == 0 ) { result = Application.run(); - Application.exit(); + Application.exit(); } std::_Exit(0); // skip destructors, there are ordering errors which causes segfaults return result; diff --git a/Float3d.h b/Float3d.h index 7efe6ba3..2ff2bcc2 100644 --- a/Float3d.h +++ b/Float3d.h @@ -212,7 +212,7 @@ public: void deserialize_float64(std::istream&); void serialize_float32(std::ostream&); float4x4(void){}; - float4x4(float f[16]) + float4x4(const float f[16]) { for (int i = 0; i < 16; ++i) e[i] = f[i]; diff --git a/Globals.cpp b/Globals.cpp index d2fccccc..5000cde0 100644 --- a/Globals.cpp +++ b/Globals.cpp @@ -943,7 +943,7 @@ global_settings::ConfigParse(cParser &Parser) { extra_viewports.push_back(conf); if (gl::vao::use_vao && conf.monitor != "MAIN") { gl::vao::use_vao = false; - WriteLog("using multiple viewports, disabling vao!"); + WriteLog("using multiple windows, disabling vao!"); } } else if (token == "map.highlightdistance") { @@ -968,6 +968,14 @@ global_settings::ConfigParse(cParser &Parser) { Parser >> headtrack_conf.rot_axes[0] >> headtrack_conf.rot_axes[1] >> headtrack_conf.rot_axes[2]; Parser >> headtrack_conf.rot_mul[0] >> headtrack_conf.rot_mul[1] >> headtrack_conf.rot_mul[2]; } + else if (token == "vr.enabled") { + Parser.getTokens(1); + Parser >> vr; + } + else if (token == "vr.backend") { + Parser.getTokens(1); + Parser >> vr_backend; + } } while ((token != "") && (token != "endconfig")); //(!Parser->EndOfFile) // na koniec trochę zależności if (!bLoadTraction) // wczytywanie drutów i słupów @@ -975,6 +983,10 @@ global_settings::ConfigParse(cParser &Parser) { bEnableTraction = false; // false = pantograf się nie połamie bLiveTraction = false; // false = pantografy zawsze zbierają 95% MaxVoltage } + if (vr) { + gfx_skippipeline = false; + VSync = false; + } /* fFpsMin = fFpsAverage - fFpsDeviation; // dolna granica FPS, przy której promień scenerii będzie zmniejszany diff --git a/Globals.h b/Globals.h index cc5500a2..5dbc3148 100644 --- a/Globals.h +++ b/Globals.h @@ -231,6 +231,8 @@ struct global_settings { bool gfx_extraeffects = true; bool gfx_shadergamma = false; bool gfx_usegles = false; + bool vr = false; + std::string vr_backend; float map_highlight_distance = 3000.0f; @@ -271,7 +273,7 @@ struct global_settings { headtrack_config headtrack_conf; glm::vec3 viewport_move; - glm::vec3 viewport_rotate; + glm::mat3 viewport_rotate; std::vector> network_servers; std::optional> network_client; diff --git a/Model3d.cpp b/Model3d.cpp index 43bc67e0..c229f6fa 100644 --- a/Model3d.cpp +++ b/Model3d.cpp @@ -61,7 +61,7 @@ void TSubModel::Name_Material(std::string const &Name) // ile nie jest wczytany z E3D if (iFlags & 0x0200) { // tylko jeżeli submodel zosta utworzony przez new - m_materialname = Name; + m_materialname = Name; } }; @@ -1244,6 +1244,16 @@ void TSubModel::ParentMatrix( float4x4 *m ) const { */ }; +void TSubModel::ReplaceMatrix(const glm::mat4 &mat) +{ + *fMatrix = float4x4(glm::value_ptr(mat)); +} + +void TSubModel::ReplaceMaterial(const std::string &name) +{ + m_material = GfxRenderer.Fetch_Material(name); +} + // obliczenie maksymalnej wysokości, na początek ślizgu w pantografie float TSubModel::MaxY( float4x4 const &m ) { // tylko dla trójkątów liczymy @@ -1926,6 +1936,32 @@ void TModel3d::LoadFromBinFile(std::string const &FileName, bool dynamic) WriteLog( "Finished loading 3d model data from \"" + FileName + "\"", logtype::model ); }; +TSubModel* TModel3d::AppendChildFromGeometry(const std::string &name, const std::string &parent, const gfx::vertex_array &data) +{ + iFlags |= 0x0200; + + TSubModel *sm = new TSubModel(); + sm->Parent = AddToNamed(parent.c_str(), sm); + sm->iNumVerts = data.size(); + sm->eType = GL_TRIANGLES; + sm->pName = name; + sm->m_material = GfxRenderer.Fetch_Material("colored"); + sm->fMatrix = new float4x4(); + sm->fMatrix->Identity(); + sm->iFlags |= 0x10; + sm->iFlags |= 0x8000; + sm->WillBeAnimated(); + if (data.empty()) + sm->iFlags &= ~0x3F; + sm->Vertices = data; + iNumVerts += data.size(); + + if (!Root) + Root = sm; + + return sm; +} + void TModel3d::LoadFromTextFile(std::string const &FileName, bool dynamic) { // wczytanie submodelu z pliku tekstowego WriteLog( "Loading text format 3d model data from \"" + FileName + "\"...", logtype::model ); diff --git a/Model3d.h b/Model3d.h index 83fa3c76..3bff57c6 100644 --- a/Model3d.h +++ b/Model3d.h @@ -162,7 +162,7 @@ public: static std::string *pasText; // tekst dla wyświetlacza (!!!! do przemyślenia) TSubModel() = default; ~TSubModel(); - int Load(cParser &Parser, TModel3d *Model, /*int Pos,*/ bool dynamic); + int Load(cParser &Parser, TModel3d *Model, /*int Pos,*/ bool dynamic); void ChildAdd(TSubModel *SubModel); void NextAdd(TSubModel *SubModel); TSubModel * NextGet() { return Next; }; @@ -217,6 +217,8 @@ public: material_handle GetMaterial() const { return m_material; } void ParentMatrix(float4x4 *m) const; + void ReplaceMatrix(const glm::mat4 &mat); + void ReplaceMaterial(const std::string &name); float MaxY( float4x4 const &m ); std::shared_ptr> screen_touch_list; // for python screen touching std::optional occlusion_query; @@ -267,7 +269,8 @@ public: void AddTo(TSubModel *tmp, TSubModel *SubModel); void LoadFromTextFile(std::string const &FileName, bool dynamic); void LoadFromBinFile(std::string const &FileName, bool dynamic); - bool LoadFromFile(std::string const &FileName, bool dynamic); + bool LoadFromFile(std::string const &FileName, bool dynamic); + TSubModel *AppendChildFromGeometry(const std::string &name, const std::string &parent, const gfx::vertex_array &data); void SaveToBinFile(std::string const &FileName); uint32_t Flags() const { return iFlags; }; void Init(); diff --git a/Texture.cpp b/Texture.cpp index 1c98c151..2828d319 100644 --- a/Texture.cpp +++ b/Texture.cpp @@ -164,7 +164,7 @@ opengl_texture::load() { if (data_state == resource_state::good) return; - if( type == "make:" ) { + if( type == "make:" || type == "internalsrc:" ) { // for generated texture we delay data creation until texture is bound // this ensures the script will receive all simulation data it might potentially want // as any binding will happen after simulation is loaded, initialized and running @@ -279,8 +279,8 @@ void opengl_texture::load_STBI() } void -opengl_texture::make_stub() { - +opengl_texture::make_stub() +{ data_width = 2; data_height = 2; data.resize( data_width * data_height * 3 ); @@ -289,6 +289,26 @@ opengl_texture::make_stub() { data_format = GL_RGB; data_components = GL_RGB; data_state = resource_state::good; + + is_texstub = true; +} + +void +opengl_texture::make_from_memory(size_t width, size_t height, const uint8_t *raw) +{ + release(); + + data_width = width; + data_height = width; + data.resize(data_width * data_height * 4); + memcpy(data.data(), raw, data.size()); + + data_format = GL_RGBA; + data_components = GL_RGBA; + data_mapcount = 1; + data_state = resource_state::good; + + is_texstub = false; } void @@ -1113,8 +1133,9 @@ texture_manager::create(std::string Filename, bool const Loadnow , GLint fh) { // discern textures generated by a script // TBD: support file: for file resources? auto const isgenerated { Filename.find( "make:" ) == 0 }; + auto const isinternalsrc { Filename.find( "internal_src:" ) == 0 }; // process supplied resource name - if( isgenerated ) { + if( isgenerated || isinternalsrc ) { // generated resource // scheme:(user@)path?query @@ -1159,6 +1180,10 @@ texture_manager::create(std::string Filename, bool const Loadnow , GLint fh) { locator.first = Filename; locator.second = "make:"; } + else if ( isinternalsrc ) { + locator.first = Filename; + locator.second = "internalsrc:"; + } else { // ...for file resources check if it's on disk locator = find_on_disk( Filename ); diff --git a/Texture.h b/Texture.h index d72ad702..9da13735 100644 --- a/Texture.h +++ b/Texture.h @@ -42,8 +42,13 @@ struct opengl_texture { int height() const { return data_height; } + inline + bool + is_stub() const { + return is_texstub; } void make_stub(); + void make_from_memory(size_t width, size_t height, const uint8_t *data); void alloc_rendertarget(GLint format, GLint components, int width, int height, int samples = 1, GLint wrap = GL_CLAMP_TO_BORDER); void set_components_hint(GLint hint); static void reset_unit_cache(); @@ -77,6 +82,7 @@ private: // members bool is_rendertarget = false; // is used as postfx rendertarget, without loaded data int samples = 1; + bool is_texstub = false; // for make_from_memory internal_src: functionality std::vector data; // texture data (stored GL-style, bottom-left origin) resource_state data_state{ resource_state::none }; // current state of texture data diff --git a/application.cpp b/application.cpp index fc049f84..f11edc08 100644 --- a/application.cpp +++ b/application.cpp @@ -354,6 +354,7 @@ eu07_application::exit() { for (auto &mode : m_modes) mode.reset(); + GfxRenderer.Shutdown(); m_network.reset(); SafeDelete( simulation::Train ); diff --git a/gl/framebuffer.cpp b/gl/framebuffer.cpp index 6c347ab9..ada531ba 100644 --- a/gl/framebuffer.cpp +++ b/gl/framebuffer.cpp @@ -74,11 +74,19 @@ void gl::framebuffer::blit(framebuffer *src, framebuffer *dst, int sx, int sy, i { int attachment_n = attachment - GL_COLOR_ATTACHMENT0; - GLenum outputs[8] = { GL_NONE }; - outputs[attachment_n] = attachment; + { + GLenum outputs[8] = { GL_NONE }; + outputs[attachment_n] = src != 0 ? attachment : GL_BACK_LEFT; - glReadBuffer(attachment); - glDrawBuffers(attachment_n + 1, outputs); + glReadBuffer(attachment); + } + + { + GLenum outputs[8] = { GL_NONE }; + outputs[attachment_n] = dst != 0 ? attachment : GL_BACK_LEFT; + + glDrawBuffers(attachment_n + 1, outputs); + } } glBlitFramebuffer(sx, sy, sx + w, sy + h, 0, 0, w, h, mask, GL_NEAREST); diff --git a/headtrack.cpp b/headtrack.cpp index 2e78ded9..c60910d1 100644 --- a/headtrack.cpp +++ b/headtrack.cpp @@ -35,7 +35,7 @@ void headtrack::update() { if (joy_id == -1 || !glfwJoystickPresent(joy_id)) { Global.viewport_move = glm::vec3(); - Global.viewport_rotate = glm::vec3(); + Global.viewport_rotate = glm::mat3(); find_joy(); return; } @@ -59,5 +59,5 @@ void headtrack::update() rotate.z = get_axis(axes, count, rot_axes.z, rot_mul.z); Global.viewport_move = move; - Global.viewport_rotate = rotate; + Global.viewport_rotate = glm::orientate3(rotate); } diff --git a/material.cpp b/material.cpp index f70d8e22..33d89105 100644 --- a/material.cpp +++ b/material.cpp @@ -313,7 +313,7 @@ material_manager::create( std::string const &Filename, bool const Loadnow ) { // discern references to textures generated by a script // TBD: support file: for file resources? - auto const isgenerated { filename.find( "make:" ) == 0 }; + auto const isgenerated { filename.find( "make:" ) == 0 || filename.find( "internal_src:" ) == 0 }; // process supplied resource name if( isgenerated ) { diff --git a/network/backend/asio.cpp b/network/backend/asio.cpp index a8f3d674..2acd4d8f 100644 --- a/network/backend/asio.cpp +++ b/network/backend/asio.cpp @@ -147,11 +147,11 @@ network::tcp::server::server(std::shared_ptr buf, asio::io_context } void network::tcp::server::accept_conn() -{ +{/* std::shared_ptr conn = std::make_shared(m_acceptor.get_executor().context()); conn->set_handler(std::bind(&server::handle_message, this, conn, std::placeholders::_1)); - m_acceptor.async_accept(conn->m_socket, std::bind(&server::handle_accept, this, conn, std::placeholders::_1)); + m_acceptor.async_accept(conn->m_socket, std::bind(&server::handle_accept, this, conn, std::placeholders::_1));*/ } void network::tcp::server::handle_accept(std::shared_ptr conn, const asio::error_code &err) diff --git a/renderer.cpp b/renderer.cpp index 873caf46..5588b9af 100644 --- a/renderer.cpp +++ b/renderer.cpp @@ -291,6 +291,33 @@ bool opengl_renderer::Init(GLFWwindow *Window) default_viewport.window = m_window; default_viewport.draw_range = 1.0f; + if (Global.vr) + vr = vr_interface_factory::get_instance()->create(Global.vr_backend); + + if (vr) { + glm::ivec2 target_size = vr->get_target_size(); + WriteLog("using vr rendertarget: " + glm::to_string(target_size)); + + // hijack main window for left eye + default_viewport.width = target_size.x; + default_viewport.height = target_size.y; + default_viewport.custom_backbuffer = true; + default_viewport.proj_type = viewport_config::vr_left; + + // create right eye viewport + m_viewports.push_back(std::make_unique()); + m_viewports.back()->width = target_size.x; + m_viewports.back()->height = target_size.y; + m_viewports.back()->window = m_window; // we can reuse context + m_viewports.back()->real_window = false; // but don't draw anything to it + m_viewports.back()->custom_backbuffer = true; + m_viewports.back()->proj_type = viewport_config::vr_right; + m_viewports.back()->draw_range = 1.0f; + + if (!init_viewport(*m_viewports.back())) + return false; + } + if (!init_viewport(default_viewport)) return false; glfwMakeContextCurrent(m_window); @@ -418,6 +445,11 @@ bool opengl_renderer::Init(GLFWwindow *Window) return true; } +void opengl_renderer::Shutdown() +{ + vr.reset(); +} + bool opengl_renderer::AddViewport(const global_settings::extraviewport_config &conf) { viewport_config *vp; @@ -434,7 +466,7 @@ bool opengl_renderer::AddViewport(const global_settings::extraviewport_config &c vp->width = conf.width; vp->height = conf.height; vp->projection = conf.projection; - vp->custom_projection = true; + vp->proj_type = viewport_config::custom; vp->draw_range = conf.draw_range; bool ret = init_viewport(*vp); @@ -453,7 +485,8 @@ bool opengl_renderer::init_viewport(viewport_config &vp) WriteLog("init viewport: " + std::to_string(vp.width) + ", " + std::to_string(vp.height)); - glfwSwapInterval( Global.VSync ? 1 : 0 ); + if (vp.real_window) + glfwSwapInterval( Global.VSync ? 1 : 0 ); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1); @@ -542,6 +575,17 @@ bool opengl_renderer::init_viewport(viewport_config &vp) return false; } + if (vp.custom_backbuffer) + { + vp.backbuffer_tex = std::make_unique(); + vp.backbuffer_tex->alloc_rendertarget(GL_SRGB8, GL_RGB, vp.width, vp.height); + + vp.backbuffer_fb = std::make_unique(); + vp.backbuffer_fb->attach(*vp.backbuffer_tex, GL_COLOR_ATTACHMENT0); + if (!vp.backbuffer_fb->is_complete()) + return false; + } + vp.initialized = true; return true; } @@ -579,6 +623,9 @@ bool opengl_renderer::Render() m_renderpass.draw_mode = rendermode::none; // force setup anew m_debugstats = debug_stats(); + if (vr) + vr->begin_frame(); + for (auto &viewport : m_viewports) { Render_pass(*viewport, rendermode::color); } @@ -620,10 +667,13 @@ void opengl_renderer::SwapBuffers() Timer::subsystem.gfx_swap.start(); for (auto &viewport : m_viewports) { - if (viewport->window) + if (viewport->window && viewport->real_window) glfwSwapBuffers(viewport->window); } + if (vr) + vr->finish_frame(); + // swapbuffers() could unbind current buffers so we prepare for it on our end gfx::opengl_vbogeometrybank::reset(); Timer::subsystem.gfx_swap.stop(); @@ -725,7 +775,7 @@ void opengl_renderer::Render_pass(viewport_config &vp, rendermode const Mode) setup_drawing(false); glm::ivec2 target_size(vp.width, vp.height); - if (vp.main) // TODO: update window sizes also for extra viewports + if (vp.main && !vp.custom_backbuffer) // TODO: update window sizes also for extra viewports target_size = glm::ivec2(Global.iWindowWidth, Global.iWindowHeight); if (!Global.gfx_skippipeline) @@ -745,7 +795,8 @@ void opengl_renderer::Render_pass(viewport_config &vp, rendermode const Mode) { if (!Global.gfx_usegles && !Global.gfx_shadergamma) glEnable(GL_FRAMEBUFFER_SRGB); - glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); glViewport(0, 0, target_size.x, target_size.y); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); } @@ -796,6 +847,8 @@ void opengl_renderer::Render_pass(viewport_config &vp, rendermode const Mode) Render(simulation::Region); + Render_vr_models(); + // ...translucent parts glDebug("render translucent region"); setup_drawing(true); @@ -856,9 +909,25 @@ void opengl_renderer::Render_pass(viewport_config &vp, rendermode const Mode) glEnable(GL_FRAMEBUFFER_SRGB); glViewport(0, 0, target_size.x, target_size.y); - m_pfx_tonemapping->apply(*vp.main2_tex, nullptr); + + gl::framebuffer *target = nullptr; + if (vp.custom_backbuffer) + target = vp.backbuffer_fb.get(); + m_pfx_tonemapping->apply(*vp.main2_tex, target); + + if (vr) { + if (vp.proj_type == viewport_config::vr_left) + vr->submit(vr_interface::eye_left, vp.backbuffer_tex.get()); + if (vp.proj_type == viewport_config::vr_right) + vr->submit(vr_interface::eye_right, vp.backbuffer_tex.get()); + } + + if (vp.custom_backbuffer && vp.real_window) { + vp.backbuffer_fb->blit_to(nullptr, vp.width, vp.height, GL_COLOR_BUFFER_BIT, GL_COLOR_ATTACHMENT0); + } + opengl_texture::reset_unit_cache(); - } + } if (!Global.gfx_usegles && !Global.gfx_shadergamma) glDisable(GL_FRAMEBUFFER_SRGB); @@ -900,6 +969,8 @@ void opengl_renderer::Render_pass(viewport_config &vp, rendermode const Mode) scene_ubo->update(scene_ubs); Render(simulation::Region); + Render_vr_models(); + m_shadowpass = m_renderpass; m_shadow_fb->unbind(); @@ -1210,46 +1281,43 @@ void opengl_renderer::setup_pass(viewport_config &Viewport, renderpass_config &C glm::mat4 frustumtest_proj; - if (!Viewport.custom_projection && Viewport.main) { - // TODO: update window sizes also for extra viewports - float const fovy = glm::radians(Global.FieldOfView / Global.ZoomFactor); - - // setup virtual screen - glm::vec2 screen_h = glm::vec2(Global.iWindowWidth, Global.iWindowHeight) / 2.0f; - float const dist = screen_h.y / glm::tan(fovy / 2.0f); - - Viewport.projection.pa = glm::vec3(-screen_h.x, -screen_h.y, -dist); - Viewport.projection.pb = glm::vec3( screen_h.x, -screen_h.y, -dist); - Viewport.projection.pc = glm::vec3(-screen_h.x, screen_h.y, -dist); - Viewport.projection.pe = glm::vec3(0.0f, 0.0f, 0.0f); - } - - if (Global.headtrack_conf.magic_window) - Viewport.projection.pe = Global.viewport_move; - Config.viewport_camera.position() = Global.pCamera.Pos; switch (Mode) { case rendermode::color: { - if (!Global.headtrack_conf.magic_window) { - Global.pCamera.Pos += Global.viewport_move; - Global.pCamera.LookAt += Global.viewport_move; - Global.pCamera.Angle += Global.viewport_rotate; + if (Viewport.proj_type == viewport_config::normal && Viewport.main) { + // TODO: update window sizes also for extra viewports + float const fovy = glm::radians(Global.FieldOfView / Global.ZoomFactor); + + // setup virtual screen + glm::vec2 screen_h = glm::vec2(Global.iWindowWidth, Global.iWindowHeight) / 2.0f; + float const dist = screen_h.y / glm::tan(fovy / 2.0f); + + Viewport.projection.pa = glm::vec3(-screen_h.x, -screen_h.y, -dist); + Viewport.projection.pb = glm::vec3( screen_h.x, -screen_h.y, -dist); + Viewport.projection.pc = glm::vec3(-screen_h.x, screen_h.y, -dist); + Viewport.projection.pe = glm::vec3(0.0f, 0.0f, 0.0f); } - // modelview - if ((false == DebugCameraFlag) || (true == Ignoredebug)) - { - camera.position() = Global.pCamera.Pos; - Global.pCamera.SetMatrix(viewmatrix); - } - else - { - camera.position() = Global.pDebugCamera.Pos; - Global.pDebugCamera.SetMatrix(viewmatrix); - } + if (vr && (Viewport.proj_type == viewport_config::vr_left || Viewport.proj_type == viewport_config::vr_right)) { + Viewport.projection = vr->get_proj_config(Viewport.proj_type == viewport_config::vr_left ? vr_interface::eye_left : vr_interface::eye_right); + Global.pCamera.Angle.x = 0.0; + Global.pCamera.Angle.z = 0.0; + } + + if (Global.headtrack_conf.magic_window) + Viewport.projection.pe = Global.viewport_move; + + TCamera *cam = ((false == DebugCameraFlag) || (true == Ignoredebug)) ? &Global.pCamera : &Global.pDebugCamera; + camera.position() = cam->Pos; + cam->SetMatrix(viewmatrix); + + if (!Global.headtrack_conf.magic_window || vr) { + camera.position() += Global.viewport_move * glm::mat3(viewmatrix); + viewmatrix = glm::dmat4(glm::inverse(Global.viewport_rotate)) * viewmatrix; + } // projection auto const zfar = Config.draw_range * Global.fDistanceFactor * Zfar; @@ -1320,12 +1388,18 @@ void opengl_renderer::setup_pass(viewport_config &Viewport, renderpass_config &C break; } case rendermode::cabshadows: - { + { + renderpass_config worldview; + setup_pass(Viewport, worldview, rendermode::color, 0.f, Zfar, true); + //glm::dmat4 tmpmat; + //Global.pCamera.SetMatrix(tmpmat); + // fixed size cube large enough to enclose a vehicle compartment // modelview auto const lightvector = glm::normalize(glm::vec3{m_sunlight.direction.x, std::min(m_sunlight.direction.y, -0.2f), m_sunlight.direction.z}); - camera.position() = Global.pCamera.Pos - glm::dvec3{lightvector}; - viewmatrix *= glm::lookAt(camera.position(), glm::dvec3{Global.pCamera.Pos}, glm::dvec3{0.f, 1.f, 0.f}); + camera.position() = worldview.pass_camera.position() - glm::dvec3{lightvector}; + viewmatrix *= glm::lookAt(camera.position(), worldview.pass_camera.position(), glm::dvec3{0.f, 1.f, 0.f}); + // projection auto const maphalfsize{Config.draw_range * 0.5f}; camera.projection() = ortho_projection(-maphalfsize, maphalfsize, -maphalfsize, maphalfsize, -Config.draw_range, Config.draw_range); @@ -2494,7 +2568,7 @@ bool opengl_renderer::Render_cab(TDynamicObject const *Dynamic, float const Ligh else { // opaque parts - Render(Dynamic->mdKabina, Dynamic->Material(), 0.0); + Render(Dynamic->mdKabina, Dynamic->Material(), 0.0); } // post-render restore if (Dynamic->fShade > 0.0f) @@ -3020,6 +3094,25 @@ void opengl_renderer::Render_particles() m_particlerenderer.render(); } +void opengl_renderer::Render_vr_models() +{ + if (!vr) + return; + + glDebug("Render_vr_models"); + + ::glPushMatrix(); + glm::dmat4 tmpmat; + Global.pCamera.SetMatrix(tmpmat); + tmpmat = glm::dmat3(tmpmat); + glMultMatrixd(glm::value_ptr(glm::inverse(tmpmat))); + std::vector list = vr->get_render_models(); + material_data data; + for (TModel3d *mdl : list) + Render(mdl, &data, 0.0); + ::glPopMatrix(); +} + void opengl_renderer::Render_precipitation() { if (Global.Overcast <= 1.f) diff --git a/renderer.h b/renderer.h index 2616b5d7..43b12f37 100644 --- a/renderer.h +++ b/renderer.h @@ -20,6 +20,7 @@ http://mozilla.org/MPL/2.0/. #include "scene.h" #include "light.h" #include "particles.h" +#include "vr/vr_interface.h" #include "gl/ubo.h" #include "gl/framebuffer.h" #include "gl/renderbuffer.h" @@ -167,6 +168,7 @@ class opengl_renderer // methods bool Init(GLFWwindow *Window); + void Shutdown(); bool AddViewport(const global_settings::extraviewport_config &conf); // main draw call. returns false on error @@ -281,23 +283,37 @@ class opengl_renderer float draw_range; bool main = false; - GLFWwindow *window = nullptr; + GLFWwindow *window = nullptr; // ogl window context + bool real_window = true; // whether we need to blit onto GLFWwindow surface + bool custom_backbuffer = false; // whether we want to render to our offscreen LDR backbuffer (pipeline required) - bool custom_projection = false; + enum vp_type { + normal, + custom, + vr_left, + vr_right + } proj_type; viewport_proj_config projection; + // main msaa render target for pipeline mode std::unique_ptr msaa_fb; std::unique_ptr msaa_rbc; std::unique_ptr msaa_rbv; std::unique_ptr msaa_rbd; + // msaa resolve buffer (when using motion blur) std::unique_ptr main_fb; std::unique_ptr main_texv; std::unique_ptr main_tex; + // final HDR buffer (also serving as msaa resolve buffer when not using motion blur) std::unique_ptr main2_fb; std::unique_ptr main2_tex; + // LDR backbuffer for offscreen rendering + std::unique_ptr backbuffer_fb; + std::unique_ptr backbuffer_tex; + bool initialized = false; }; @@ -334,6 +350,7 @@ class opengl_renderer void Render(TMemCell *Memcell); void Render_particles(); void Render_precipitation(); + void Render_vr_models(); void Render_Alpha(scene::basic_region *Region); void Render_Alpha(cell_sequence::reverse_iterator First, cell_sequence::reverse_iterator Last); void Render_Alpha(TAnimModel *Instance); @@ -497,6 +514,8 @@ class opengl_renderer }; headlight_config_s headlight_config; + + std::unique_ptr vr; }; extern opengl_renderer GfxRenderer; diff --git a/stdafx.h b/stdafx.h index 4b8e76d3..726ea7b2 100644 --- a/stdafx.h +++ b/stdafx.h @@ -95,6 +95,7 @@ #include #include #include +#include #include #include @@ -106,4 +107,5 @@ int const null_handle = 0; #define glDebug(x) if (GLAD_GL_GREMEDY_string_marker) glStringMarkerGREMEDY(0, __FILE__ ":" STRINGIZE(__LINE__) ": " x); #include "imgui/imgui.h" + #endif diff --git a/vr/openvr_imp.cpp b/vr/openvr_imp.cpp new file mode 100644 index 00000000..3fca4381 --- /dev/null +++ b/vr/openvr_imp.cpp @@ -0,0 +1,293 @@ +#include "stdafx.h" +#include "openvr_imp.h" +#include "Logs.h" +#include "Globals.h" +#include "renderer.h" + +vr_openvr::vr_openvr() +{ + vr::EVRInitError vr_error; + vr_system = vr::VR_Init(&vr_error, vr::VRApplication_Scene); + if (vr_error != vr::VRInitError_None) { + ErrorLog("failed to init openvr!"); + return; + } + + vr::VRCompositor()->SetTrackingSpace(vr::TrackingUniverseSeated); + vr::VRInput()->GetInputSourceHandle("/user/hand/left", &inputhandle_left); + vr::VRInput()->GetInputSourceHandle("/user/hand/right", &inputhandle_right); + + std::string action_path = std::filesystem::current_path().string() + "/openvr_actions.json"; + vr::VRInput()->SetActionManifestPath(action_path.c_str()); + + vr::VRInput()->GetActionSetHandle("/actions/main", &actionset); + vr::VRInput()->GetActionHandle("/actions/main/in/PrimaryAction", &primary_action); +} + +glm::ivec2 vr_openvr::get_target_size() +{ + uint32_t vr_w, vr_h; + vr_system->GetRecommendedRenderTargetSize(&vr_w, &vr_h); + return glm::ivec2(vr_w, vr_h); +} + +viewport_proj_config vr_openvr::get_proj_config(eye_e e) +{ + vr::EVREye eye = (e == vr_interface::eye_left) ? vr::Eye_Left : vr::Eye_Right; + + float left, right, top, bottom; // tangents of half-angles from center view axis + vr_system->GetProjectionRaw(eye, &left, &right, &top, &bottom); + + float ipd = vr_system->GetEyeToHeadTransform(eye).m[0][3]; + + viewport_proj_config proj; + + proj.pe = glm::vec3(ipd, 0.0f, 0.0f); + + proj.pa = glm::vec3(ipd + left, top, -1.0f); + proj.pc = glm::vec3(ipd + left, bottom, -1.0f); + + proj.pb = glm::vec3(ipd + right, top, -1.0f); + + return proj; +} + +glm::mat4 vr_openvr::get_matrix(vr::HmdMatrix34_t &src) +{ + return glm::mat4(glm::transpose(glm::make_mat3x4((float*)(src.m)))); +} + +void vr_openvr::begin_frame() +{ + vr::TrackedDevicePose_t devicePose[MAX_DEVICES]; + vr::VRCompositor()->WaitGetPoses(devicePose, MAX_DEVICES, nullptr, 0); + + for (size_t i = 0; i < MAX_DEVICES; i++) { + if (!vr_system->IsTrackedDeviceConnected(i)) { + if (controllers[i]) + controllers[i].reset(); + + continue; + } + + glm::mat4 device_pose = get_matrix(devicePose[i].mDeviceToAbsoluteTracking); + + vr::ETrackedDeviceClass device_class = vr_system->GetTrackedDeviceClass(i); + + if (device_class == vr::TrackedDeviceClass_HMD) { + Global.viewport_move = glm::vec3(device_pose[3]); + Global.viewport_rotate = glm::mat3(device_pose); + } + + if (device_class == vr::TrackedDeviceClass_Controller) { + if (!controllers[i]) { + controllers[i] = std::make_shared(); + + vr::ETrackedControllerRole role = (vr::ETrackedControllerRole)vr_system->GetInt32TrackedDeviceProperty(i, vr::Prop_ControllerRoleHint_Int32); + if (role == vr::TrackedControllerRole_LeftHand) + controllers[i]->inputhandle = inputhandle_left; + else if (role == vr::TrackedControllerRole_RightHand) + controllers[i]->inputhandle = inputhandle_right; + + char model_name[128]; + size_t ret = vr_system->GetStringTrackedDeviceProperty(i, vr::Prop_RenderModelName_String, model_name, sizeof(model_name)); + + if (ret != 0) { + controllers[i]->rendermodel = std::string(model_name); + + uint32_t component_count = vr::VRRenderModels()->GetComponentCount(model_name); + if (component_count == 0 || !controllers[i]->inputhandle) { + controllers[i]->pending_components.push_back(-1); + } else { + for (size_t c = 0; c < component_count; c++) + controllers[i]->pending_components.push_back(c); + } + + controllers[i]->model = std::make_unique(); + controllers[i]->model->AppendChildFromGeometry("__root", "none", gfx::vertex_array()); + } + } + + for (size_t c = controllers[i]->pending_components.size(); c-- != 0;) { + uint32_t component = controllers[i]->pending_components[c]; + const char *rendermodel = controllers[i]->rendermodel.c_str(); + + char rendermodel_name[256]; + char component_name[128]; + + gfx::vertex_array data; + std::string submodel_name; + vr::RenderModel_t *model; + vr::EVRRenderModelError ret; + TSubModel *sm; + + if (component == -1) { + const char *name = controllers[i]->rendermodel.c_str(); + strncpy(rendermodel_name, name, sizeof(rendermodel_name) - 1); + rendermodel_name[255] = 0; + } + else { + vr::VRRenderModels()->GetComponentName(rendermodel, component, component_name, sizeof(component_name)); + if (!vr::VRRenderModels()->GetComponentRenderModelName(rendermodel, component_name, rendermodel_name, sizeof(rendermodel_name))) + goto component_done; + } + + ret = vr::VRRenderModels()->LoadRenderModel_Async(rendermodel_name, &model); + + if (ret == vr::VRRenderModelError_Loading) + continue; + else if (ret != vr::VRRenderModelError_None) + goto component_done; + + data.resize(model->unTriangleCount * 3); + + for (size_t v = 0; v < model->unTriangleCount * 3; v++) { + const vr::RenderModel_Vertex_t *vertex = &model->rVertexData[model->rIndexData[v]]; + data[v] = gfx::basic_vertex( + glm::vec3(vertex->vPosition.v[0], vertex->vPosition.v[1], vertex->vPosition.v[2]), + glm::vec3(vertex->vNormal.v[0], vertex->vNormal.v[1], vertex->vNormal.v[2]), + glm::vec2(vertex->rfTextureCoord[0], vertex->rfTextureCoord[1])); + } + + submodel_name = std::string(component == -1 ? rendermodel_name : component_name); + sm = controllers[i]->model->AppendChildFromGeometry(submodel_name, "__root", data); + + if (model->diffuseTextureId >= 0) + controllers[i]->pending_textures.push_back(std::make_pair(sm, model->diffuseTextureId)); + + vr::VRRenderModels()->FreeRenderModel(model); + + if (component != -1 && update_component(controllers[i]->rendermodel, controllers[i]->inputhandle, sm)) + controllers[i]->active_components.push_back(sm); + +component_done: + controllers[i]->pending_components.erase(controllers[i]->pending_components.begin() + c); + + if (controllers[i]->pending_components.empty()) + controllers[i]->model->Init(); + } + + for (size_t t = controllers[i]->pending_textures.size(); t-- != 0;) { + TSubModel *sm = controllers[i]->pending_textures[t].first; + const vr::TextureID_t texture_id = controllers[i]->pending_textures[t].second; + + if (GfxRenderer.Material(sm->GetMaterial()).textures[0] == null_handle) + sm->ReplaceMaterial("internal_src:OPENVR" + std::to_string(texture_id)); + opengl_texture &tex = GfxRenderer.Texture(GfxRenderer.Material(sm->GetMaterial()).textures[0]); + + if (tex.is_stub()) { + vr::RenderModel_TextureMap_t *texturemap; + auto ret = vr::VRRenderModels()->LoadTexture_Async(texture_id, &texturemap); + + if (ret == vr::VRRenderModelError_Loading) + continue; + else if (ret != vr::VRRenderModelError_None) + goto texture_done; + + if (texturemap->format == vr::VRRenderModelTextureFormat_RGBA8_SRGB) + tex.make_from_memory(texturemap->unWidth, texturemap->unHeight, texturemap->rubTextureMapData); + else + ErrorLog("openvr texture format " + std::to_string(texturemap->format) + " not supported"); + + vr::VRRenderModels()->FreeTexture(texturemap); + } + +texture_done: + controllers[i]->pending_textures.erase(controllers[i]->pending_textures.begin() + t); + } + + if (controllers[i]->model) { + glm::vec3 trans = glm::vec3(device_pose[3]); + glm::mat3 rot = glm::mat3(device_pose); + trans -= Global.viewport_move; + + controllers[i]->model->GetSMRoot()->ReplaceMatrix(glm::translate(glm::mat4(), trans) * glm::mat4(rot)); + + for (TSubModel *component : controllers[i]->active_components) + update_component(controllers[i]->rendermodel, controllers[i]->inputhandle, component); + } + } + } + + vr::VRActiveActionSet_t active_actionset; + active_actionset.ulActionSet = actionset; + active_actionset.ulRestrictedToDevice = vr::k_ulInvalidInputValueHandle; + active_actionset.nPriority = 0; + + vr::VRInput()->UpdateActionState(&active_actionset, sizeof(active_actionset), 1); + + vr::InputDigitalActionData_t actiondata; + vr::VRInput()->GetDigitalActionData(primary_action, &actiondata, sizeof(actiondata), vr::k_ulInvalidInputValueHandle); + if (actiondata.bChanged && actiondata.bState) { + WriteLog("VR click!"); + } + + bool prev_should_pause = should_pause; + should_pause = vr_system->ShouldApplicationPause(); + if (prev_should_pause != should_pause) + relay.post(user_command::focuspauseset, should_pause ? 1.0 : 0.0, 0.0, GLFW_PRESS, 0); + + draw_controllers = !vr_system->IsSteamVRDrawingControllers(); +} + +bool vr_openvr::update_component(const std::string &rendermodel, vr::VRInputValueHandle_t handle, TSubModel *component) +{ + vr::RenderModel_ControllerMode_State_t state; + state.bScrollWheelVisible = false; + + vr::RenderModel_ComponentState_t component_state; + + vr::VRRenderModels()->GetComponentStateForDevicePath(rendermodel.c_str(), component->pName.c_str(), handle, &state, &component_state); + + glm::mat4 component_pose = get_matrix(component_state.mTrackingToComponentRenderModel); + bool visible = (component_state.uProperties & vr::VRComponentProperty_IsVisible); + + component->ReplaceMatrix(component_pose); + component->iVisible = (visible ? 1 : 0); + + return !(component_state.uProperties & vr::VRComponentProperty_IsStatic); +} + +void vr_openvr::submit(vr_openvr::eye_e eye, opengl_texture* tex) +{ + vr::Texture_t hmd_tex = + { (void*)(uint64_t)tex->id, + vr::TextureType_OpenGL, + Global.gfx_shadergamma ? vr::ColorSpace_Linear : vr::ColorSpace_Gamma }; + + vr::VRCompositor()->Submit(eye == vr_interface::eye_left ? vr::Eye_Left : vr::Eye_Right, &hmd_tex); +} + +std::vector vr_openvr::get_render_models() +{ + std::vector list; + + if (!draw_controllers) + return list; + + for (auto entry : controllers) + { + if (entry && entry->model && entry->pending_components.empty()) + list.push_back(entry->model.get()); + } + + return list; +} + +void vr_openvr::finish_frame() +{ + vr::VRCompositor()->PostPresentHandoff(); +} + +vr_openvr::~vr_openvr() +{ + vr::VR_Shutdown(); +} + +std::unique_ptr vr_openvr::create_func() +{ + return std::unique_ptr(new vr_openvr()); +} + +bool vr_openvr::backend_register = vr_interface_factory::get_instance()->register_backend("openvr", vr_openvr::create_func); + diff --git a/vr/openvr_imp.h b/vr/openvr_imp.h new file mode 100644 index 00000000..d553bda6 --- /dev/null +++ b/vr/openvr_imp.h @@ -0,0 +1,49 @@ +#pragma once +#include "vr_interface.h" +#include + +class vr_openvr : vr_interface +{ +private: + struct controller_info { + std::unique_ptr model; + std::string rendermodel; + std::vector pending_components; + std::vector active_components; + std::vector> pending_textures; + vr::VRInputValueHandle_t inputhandle = 0; + }; + + static const size_t MAX_DEVICES = 16; + + vr::IVRSystem *vr_system = nullptr; + std::array, MAX_DEVICES> controllers; + std::unordered_map> textures; + vr::VRInputValueHandle_t inputhandle_left = 0; + vr::VRInputValueHandle_t inputhandle_right = 0; + + bool update_component(const std::string &rendermodel, vr::VRInputValueHandle_t handle, TSubModel *component); + glm::mat4 get_matrix(vr::HmdMatrix34_t &src); + + vr::VRActionSetHandle_t actionset = 0; + vr::VRActionHandle_t primary_action = 0; + bool should_pause = false; + bool draw_controllers = true; + + command_relay relay; + +public: + vr_openvr(); + viewport_proj_config get_proj_config(eye_e) override; + glm::ivec2 get_target_size() override; + void begin_frame() override; + void submit(eye_e, opengl_texture*) override; + std::vector get_render_models() override; + void finish_frame() override; + ~vr_openvr() override; + + static std::unique_ptr create_func(); + +private: + static bool backend_register; +}; diff --git a/vr/vr_interface.cpp b/vr/vr_interface.cpp new file mode 100644 index 00000000..b789bc44 --- /dev/null +++ b/vr/vr_interface.cpp @@ -0,0 +1,31 @@ +#include "stdafx.h" +#include "vr_interface.h" +#include "Logs.h" + +vr_interface::~vr_interface() {} + +bool vr_interface_factory::register_backend(const std::string &backend, vr_interface_factory::create_method func) +{ + backends[backend] = func; + return true; +} + +std::unique_ptr vr_interface_factory::create(const std::string &backend) +{ + auto it = backends.find(backend); + if (it != backends.end()) + return it->second(); + + ErrorLog("vr backend \"" + backend + "\" not found!"); + return nullptr; +} + +vr_interface_factory *vr_interface_factory::get_instance() +{ + if (!instance) + instance = new vr_interface_factory(); + + return instance; +} + +vr_interface_factory *vr_interface_factory::instance; diff --git a/vr/vr_interface.h b/vr/vr_interface.h new file mode 100644 index 00000000..acdbcb15 --- /dev/null +++ b/vr/vr_interface.h @@ -0,0 +1,36 @@ +#pragma once +#include "Camera.h" +#include "Model3d.h" +#include "Texture.h" + +class vr_interface +{ +public: + enum eye_e { + eye_left, + eye_right + }; + + virtual viewport_proj_config get_proj_config(eye_e) = 0; + virtual glm::ivec2 get_target_size() = 0; + virtual void begin_frame() = 0; + virtual void submit(eye_e, opengl_texture*) = 0; + virtual std::vector get_render_models() = 0; + virtual void finish_frame() = 0; + virtual ~vr_interface() = 0; +}; + +class vr_interface_factory +{ +public: + using create_method = std::unique_ptr(*)(); + + bool register_backend(const std::string &backend, create_method func); + std::unique_ptr create(const std::string &name); + + static vr_interface_factory* get_instance(); + +private: + std::unordered_map backends; + static vr_interface_factory *instance; +};