From 069009fc06f90900a57ad25ae4299f1672a14037 Mon Sep 17 00:00:00 2001 From: brunoherbelin Date: Sat, 9 May 2020 13:40:47 +0200 Subject: [PATCH] Session loading and saving in thread. --- ImageShader.cpp | 6 - Mixer.cpp | 265 +++++++++++++++++++++++++-------------- Mixer.h | 14 ++- Scene.cpp | 10 ++ Scene.h | 4 +- Session.cpp | 6 +- Session.h | 5 + SessionCreator.cpp | 54 +++++++- SessionCreator.h | 9 +- Shader.cpp | 16 ++- UserInterfaceManager.cpp | 45 ++++++- 11 files changed, 304 insertions(+), 130 deletions(-) diff --git a/ImageShader.cpp b/ImageShader.cpp index d619ae0..7abff29 100644 --- a/ImageShader.cpp +++ b/ImageShader.cpp @@ -50,12 +50,6 @@ void ImageShader::reset() { Shader::reset(); - // setup double texturing (TODO : do it only once) - program_->use(); - program_->setUniform("iChannel0", 0); - program_->setUniform("iChannel1", 1); - program_->enduse(); - // default mask mask = 0; custom_textureindex = mask_presets[0]; diff --git a/Mixer.cpp b/Mixer.cpp index d0dff1c..71f86d5 100644 --- a/Mixer.cpp +++ b/Mixer.cpp @@ -22,22 +22,86 @@ using namespace tinyxml2; #include "Mixer.h" // static objects for multithreaded session loading -static bool sessionLoadPending_ = false; -static bool sessionLoadFinished_ = false; -static Session *loadedSession; +static std::atomic sessionLoadPending_ = false; +static std::atomic sessionLoadFinished_ = false; +static std::string sessionThreadFilename_ = ""; -static void loadSession(const std::string& filename, ) +static void loadSession(const std::string& filename, Session *session) { sessionLoadPending_ = true; + sessionLoadFinished_ = false; + sessionThreadFilename_ = ""; + // actual loading of xml file + SessionCreator creator( session ); + if (!creator.load(filename)) { + // error loading + Log::Info("Failed to load Session file %s.", filename.c_str()); + } + else { + // loaded ok + Log::Info("Session file %s loaded. %d source(s) created.", filename.c_str(), session->numSource()); + } + + sessionThreadFilename_ = filename; sessionLoadFinished_ = true; + sessionLoadPending_ = false; } +// static objects for multithreaded session saving +static std::atomic sessionSavePending_ = false; +static std::atomic sessionSaveFinished_ = false; +static void saveSession(const std::string& filename, Session *session) +{ + // reset + sessionSavePending_ = true; + sessionSaveFinished_ = false; + sessionThreadFilename_ = ""; + // creation of XML doc + XMLDocument xmlDoc; -Mixer::Mixer() : session_(nullptr), current_view_(nullptr) + XMLElement *version = xmlDoc.NewElement(APP_NAME); + version->SetAttribute("major", XML_VERSION_MAJOR); + version->SetAttribute("minor", XML_VERSION_MINOR); + xmlDoc.InsertEndChild(version); + + // 1. list of sources + XMLElement *sessionNode = xmlDoc.NewElement("Session"); + xmlDoc.InsertEndChild(sessionNode); + SourceList::iterator iter; + for (iter = session->begin(); iter != session->end(); iter++) + { + SessionVisitor sv(&xmlDoc, sessionNode); + // source visitor + (*iter)->accept(sv); + } + + // 2. config of views + XMLElement *views = xmlDoc.NewElement("Views"); + xmlDoc.InsertEndChild(views); + { + XMLElement *mixing = xmlDoc.NewElement( "Mixing" ); + mixing->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::MIXING), &xmlDoc)); + views->InsertEndChild(mixing); + + XMLElement *geometry = xmlDoc.NewElement( "Geometry" ); + geometry->InsertEndChild( SessionVisitor::NodeToXML(*session->config(View::GEOMETRY), &xmlDoc)); + views->InsertEndChild(geometry); + } + + // save file to disk + XMLSaveDoc(&xmlDoc, filename); + + // all ok + sessionThreadFilename_ = filename; + sessionSaveFinished_ = true; + sessionSavePending_ = false; +} + +Mixer::Mixer() : session_(nullptr), back_session_(nullptr), current_view_(nullptr) { // this initializes with a new empty session newSession(); @@ -49,6 +113,17 @@ Mixer::Mixer() : session_(nullptr), current_view_(nullptr) void Mixer::update() { + // swap front and back sessions when loading is finished + if (sessionLoadFinished_) { + swap(); + sessionFilename_ = sessionThreadFilename_; + sessionLoadFinished_ = false; + } + if (sessionSaveFinished_) { + sessionFilename_ = sessionThreadFilename_; + sessionSaveFinished_ = false; + } + // compute dt if (update_time_ == GST_CLOCK_TIME_NONE) update_time_ = gst_util_get_timestamp (); @@ -244,119 +319,99 @@ View *Mixer::currentView() return current_view_; } - -void Mixer::save(const std::string& filename) +void Mixer::save() { - XMLDocument xmlDoc; - - XMLElement *version = xmlDoc.NewElement(APP_NAME); - version->SetAttribute("major", XML_VERSION_MAJOR); - version->SetAttribute("minor", XML_VERSION_MINOR); - xmlDoc.InsertEndChild(version); - - // block: list of sources - XMLElement *session = xmlDoc.NewElement("Session"); - xmlDoc.InsertEndChild(session); - SourceList::iterator iter; - for (iter = session_->begin(); iter != session_->end(); iter++) - { - SessionVisitor sv(&xmlDoc, session); - // source visitor - (*iter)->accept(sv); - } - - // block: config of views - XMLElement *views = xmlDoc.NewElement("Views"); - xmlDoc.InsertEndChild(views); - { - XMLElement *mixing = xmlDoc.NewElement( "Mixing" ); - mixing->InsertEndChild( SessionVisitor::NodeToXML(*mixing_.scene.root(), &xmlDoc)); - views->InsertEndChild(mixing); - - XMLElement *geometry = xmlDoc.NewElement( "Geometry" ); - geometry->InsertEndChild( SessionVisitor::NodeToXML(*geometry_.scene.root(), &xmlDoc)); - views->InsertEndChild(geometry); - } - - // save file - XMLSaveDoc(&xmlDoc, filename); + if (!sessionFilename_.empty()) + saveas(sessionFilename_); } -bool Mixer::open(const std::string& filename) +void Mixer::saveas(const std::string& filename) { - XMLDocument xmlDoc; - XMLError eResult = xmlDoc.LoadFile(filename.c_str()); - if (XMLResultError(eResult)) - return false; + // optional copy of views config + session_->config(View::MIXING)->copyTransform( mixing_.scene.root() ); + session_->config(View::GEOMETRY)->copyTransform( geometry_.scene.root() ); - XMLElement *version = xmlDoc.FirstChildElement(APP_NAME); - if (version == nullptr) { - Log::Warning("Not a %s session file: %s", APP_NAME, filename.c_str()); - return false; - } - int version_major = -1, version_minor = -1; - version->QueryIntAttribute("major", &version_major); // TODO incompatible if major is different? - version->QueryIntAttribute("minor", &version_minor); - if (version_major != XML_VERSION_MAJOR || version_minor != XML_VERSION_MINOR){ - Log::Warning("%s is in an older versions of session file format. Data might be lost.", filename.c_str()); - } + // launch a thread to save the session + std::thread (saveSession, filename, session_).detach(); - // ok, ready to read sources - SessionCreator creator( xmlDoc.FirstChildElement("Session") ); - Session *new_session = creator.session(); +// XMLDocument xmlDoc; - // if all good, change to new session - if (new_session) { +// XMLElement *version = xmlDoc.NewElement(APP_NAME); +// version->SetAttribute("major", XML_VERSION_MAJOR); +// version->SetAttribute("minor", XML_VERSION_MINOR); +// xmlDoc.InsertEndChild(version); - // validate new session - newSession( new_session ); +// // block: list of sources +// XMLElement *session = xmlDoc.NewElement("Session"); +// xmlDoc.InsertEndChild(session); +// SourceList::iterator iter; +// for (iter = session_->begin(); iter != session_->end(); iter++) +// { +// SessionVisitor sv(&xmlDoc, session); +// // source visitor +// (*iter)->accept(sv); +// } - // insert nodes of sources into views - SourceList::iterator source_iter; - for (source_iter = new_session->begin(); source_iter != new_session->end(); source_iter++) - { - mixing_.scene.fg()->attach( (*source_iter)->group(View::MIXING) ); - geometry_.scene.fg()->attach( (*source_iter)->group(View::GEOMETRY) ); - } +// // block: config of views +// XMLElement *views = xmlDoc.NewElement("Views"); +// xmlDoc.InsertEndChild(views); +// { +// XMLElement *mixing = xmlDoc.NewElement( "Mixing" ); +// mixing->InsertEndChild( SessionVisitor::NodeToXML(*mixing_.scene.root(), &xmlDoc)); +// views->InsertEndChild(mixing); - } - else { - Log::Warning("Session file %s not loaded.", filename.c_str()); - return false; - } +// XMLElement *geometry = xmlDoc.NewElement( "Geometry" ); +// geometry->InsertEndChild( SessionVisitor::NodeToXML(*geometry_.scene.root(), &xmlDoc)); +// views->InsertEndChild(geometry); +// } - // config of view settings (optionnal) - XMLElement *views = xmlDoc.FirstChildElement("Views"); - if (views != nullptr) { - // ok, ready to read views - SessionCreator::XMLToNode( views->FirstChildElement("Mixing"), *mixing_.scene.root()); - SessionCreator::XMLToNode( views->FirstChildElement("Geometry"), *geometry_.scene.root()); - } - - Log::Info("Session file %s loaded. %d source(s) created.", filename.c_str(), session_->numSource()); - - return true; +// // save file +// XMLSaveDoc(&xmlDoc, filename); } -void Mixer::newSession(Session *newsession) +void Mixer::open(const std::string& filename) { - // delete session & detatch nodes from views + if (back_session_) + delete back_session_; + + // create empty session + back_session_ = new Session; + + // launch a thread to load the session into back_session + std::thread (loadSession, filename, back_session_).detach(); + +} + + +void Mixer::swap() +{ + if (!back_session_) + return; + if (session_) { - // remove nodes from views + // detatch current session's nodes from views for (auto source_iter = session_->begin(); source_iter != session_->end(); source_iter++) { mixing_.scene.fg()->detatch( (*source_iter)->group(View::MIXING) ); geometry_.scene.fg()->detatch( (*source_iter)->group(View::GEOMETRY) ); } - // clear session: delete all sources - delete session_; } - // validate new session - if (newsession) - session_ = newsession; - else - session_ = new Session; + // swap back and front + Session *tmp = session_; + session_ = back_session_; + back_session_ = tmp; + + // attach new session's nodes to views + for (auto source_iter = session_->begin(); source_iter != session_->end(); source_iter++) + { + mixing_.scene.fg()->attach( (*source_iter)->group(View::MIXING) ); + geometry_.scene.fg()->attach( (*source_iter)->group(View::GEOMETRY) ); + } + + // optional copy of views config + mixing_.scene.root()->copyTransform( session_->config(View::MIXING) ); + geometry_.scene.root()->copyTransform( session_->config(View::GEOMETRY) ); // no current source current_source_ = session_->end(); @@ -364,8 +419,24 @@ void Mixer::newSession(Session *newsession) // reset timer update_time_ = GST_CLOCK_TIME_NONE; +} - // default views + +void Mixer::newSession() +{ + // delete previous back session if needed + if (back_session_) + delete back_session_; + + // create empty session + back_session_ = new Session; + + // swap current with empty + swap(); + + // default view config mixing_.restoreSettings(); geometry_.restoreSettings(); + + sessionFilename_ = "newsession.vmx"; } diff --git a/Mixer.h b/Mixer.h index 40312fd..3625a21 100644 --- a/Mixer.h +++ b/Mixer.h @@ -60,17 +60,21 @@ public: Session *session() { return session_; } // load and save session - bool open(const std::string& filename); - void save(const std::string& filename); - void newSession(Session *newsession = nullptr); - + void open(const std::string& filename); + void saveas(const std::string& filename); + void save(); + void newSession(); protected: + Session *session_; + std::string sessionFilename_; + Session *back_session_; + void swap(); + void insertSource(Source *s); void setCurrentSource(SourceList::iterator it); - Session *session_; SourceList::iterator current_source_; int current_source_index_; diff --git a/Scene.cpp b/Scene.cpp index 50da090..d8d64d8 100644 --- a/Scene.cpp +++ b/Scene.cpp @@ -50,6 +50,16 @@ Node::~Node () } +void Node::copyTransform(Node *other) +{ + if (!other) + return; + transform_ = glm::identity(); + scale_ = other->scale_; + rotation_ = other->rotation_; + translation_ = other->translation_; +} + void Node::update( float ) { // update transform matrix from attributes diff --git a/Scene.h b/Scene.h index e79d817..8be32cf 100644 --- a/Scene.h +++ b/Scene.h @@ -65,14 +65,14 @@ public: // accept all kind of visitors virtual void accept (Visitor& v); + void copyTransform(Node *other); + // public members, to manipulate with care bool visible_; uint refcount_; - glm::mat4 transform_; glm::vec3 scale_, rotation_, translation_; - }; diff --git a/Session.cpp b/Session.cpp index b4d9b9a..7539056 100644 --- a/Session.cpp +++ b/Session.cpp @@ -7,8 +7,12 @@ Session::Session() { - + config_[View::RENDERING] = new Group; + config_[View::GEOMETRY] = new Group; + config_[View::MIXING] = new Group; } + + Session::~Session() { // delete all sources diff --git a/Session.h b/Session.h index 06bcaf3..8f2dfeb 100644 --- a/Session.h +++ b/Session.h @@ -32,9 +32,14 @@ public: // result of render inline FrameBuffer *frame() const { return render_.frameBuffer(); } + // configuration for group nodes of views + inline Group *config(View::Mode m) const { return config_.at(m); } + protected: RenderView render_; SourceList sources_; + + std::map config_; }; #endif // SESSION_H diff --git a/SessionCreator.cpp b/SessionCreator.cpp index 150aba6..afebd7e 100644 --- a/SessionCreator.cpp +++ b/SessionCreator.cpp @@ -1,6 +1,7 @@ #include "SessionCreator.h" #include "Log.h" +#include "defines.h" #include "Scene.h" #include "Primitives.h" #include "Mesh.h" @@ -14,12 +15,48 @@ using namespace tinyxml2; -SessionCreator::SessionCreator(tinyxml2::XMLElement *sessionNode): Visitor() +SessionCreator::SessionCreator(Session *session): Visitor(), session_(session) { - session_ = nullptr; + xmlDoc_ = new XMLDocument; +} + +bool SessionCreator::load(const std::string& filename) +{ + XMLError eResult = xmlDoc_->LoadFile(filename.c_str()); + if ( XMLResultError(eResult)) + return false; + + XMLElement *version = xmlDoc_->FirstChildElement(APP_NAME); + if (version == nullptr) { + Log::Warning("%s is not a %s session file.", filename.c_str(), APP_NAME); + return false; + } + + int version_major = -1, version_minor = -1; + version->QueryIntAttribute("major", &version_major); // TODO incompatible if major is different? + version->QueryIntAttribute("minor", &version_minor); + if (version_major != XML_VERSION_MAJOR || version_minor != XML_VERSION_MINOR){ + Log::Warning("%s is in a different versions of session file. Loading might fail.", filename.c_str()); + return false; + } + + // ok, ready to read sources + loadSession( xmlDoc_->FirstChildElement("Session") ); + // excellent, session was created: load optionnal config + if (session_){ + loadConfig( xmlDoc_->FirstChildElement("Views") ); + } + + return true; +} + +void SessionCreator::loadSession(XMLElement *sessionNode) +{ if (sessionNode != nullptr) { - session_ = new Session; + // create a session if not provided + if (!session_) + session_ = new Session; int counter = 0; XMLElement* sourceNode = sessionNode->FirstChildElement("Source"); @@ -34,16 +71,23 @@ SessionCreator::SessionCreator(tinyxml2::XMLElement *sessionNode): Visitor() new_media_source->accept(*this); session_->addSource(new_media_source); } - // TODO : else other types of source + // TODO : create other types of source } Log::Info("Created %d Sources", counter); - } else Log::Warning("Session seems empty."); } +void SessionCreator::loadConfig(XMLElement *viewsNode) +{ + if (viewsNode != nullptr) { + // ok, ready to read views + SessionCreator::XMLToNode( viewsNode->FirstChildElement("Mixing"), *session_->config(View::MIXING)); + SessionCreator::XMLToNode( viewsNode->FirstChildElement("Geometry"), *session_->config(View::GEOMETRY)); + } +} void SessionCreator::XMLToNode(tinyxml2::XMLElement *xml, Node &n) { diff --git a/SessionCreator.h b/SessionCreator.h index cd09742..789f9b2 100644 --- a/SessionCreator.h +++ b/SessionCreator.h @@ -8,12 +8,17 @@ class Session; class SessionCreator : public Visitor { + tinyxml2::XMLDocument *xmlDoc_; tinyxml2::XMLElement *xmlCurrent_; Session *session_; -public: - SessionCreator(tinyxml2::XMLElement *root); + void loadSession(tinyxml2::XMLElement *sessionNode); + void loadConfig(tinyxml2::XMLElement *viewsNode); +public: + SessionCreator(Session *session = nullptr); + + bool load(const std::string& filename); inline Session *session() const { return session_; } // Elements of Scene diff --git a/Shader.cpp b/Shader.cpp index 8392f95..3132c67 100644 --- a/Shader.cpp +++ b/Shader.cpp @@ -50,26 +50,30 @@ bool ShadingProgram::initialized() void ShadingProgram::compile() { - const char* vcode = vertex_code_.c_str(); + const char* vcode = vertex_code_.c_str(); vertex_id_ = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex_id_, 1, &vcode, NULL); glCompileShader(vertex_id_); - const char* fcode = fragment_code_.c_str(); + const char* fcode = fragment_code_.c_str(); fragment_id_ = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment_id_, 1, &fcode, NULL); glCompileShader(fragment_id_); - checkCompileErr(); + checkCompileErr(); } void ShadingProgram::link() { - id_ = glCreateProgram(); + id_ = glCreateProgram(); glAttachShader(id_, vertex_id_); glAttachShader(id_, fragment_id_); - glLinkProgram(id_); - checkLinkingErr(); + glLinkProgram(id_); + checkLinkingErr(); + glUseProgram(id_); + glUniform1i(glGetUniformLocation(id_, "iChannel0"), 0); + glUniform1i(glGetUniformLocation(id_, "iChannel1"), 1); + glUseProgram(0); glDeleteShader(vertex_id_); glDeleteShader(fragment_id_); } diff --git a/UserInterfaceManager.cpp b/UserInterfaceManager.cpp index a8f6381..a432a3d 100644 --- a/UserInterfaceManager.cpp +++ b/UserInterfaceManager.cpp @@ -57,12 +57,14 @@ void ShowAbout(bool* p_open); // static objects for multithreaded file dialog static bool FileDialogPending_ = false; -static bool FileDialogFinished_ = false; +static bool FileDialogLoadFinished_ = false; +static bool FileDialogSaveFinished_ = false; static std::string FileDialogFilename_ = ""; static void FileDialogOpen(std::string path) { FileDialogPending_ = true; + FileDialogLoadFinished_ = false; char const * lTheOpenFileName; char const * lFilterPatterns[2] = { "*.vmx" }; @@ -78,7 +80,29 @@ static void FileDialogOpen(std::string path) FileDialogFilename_ = std::string( lTheOpenFileName ); } - FileDialogFinished_ = true; + FileDialogLoadFinished_ = true; +} + +static void FileDialogSave(std::string path) +{ + FileDialogPending_ = true; + FileDialogSaveFinished_ = false; + + char const * save_file_name; + char const * save_pattern[2] = { "*.vmx" }; + + save_file_name = tinyfd_saveFileDialog( "Save a session file", path.c_str(), 1, save_pattern, "vimix session"); + + if (!save_file_name) + { + FileDialogFilename_ = ""; + } + else + { + FileDialogFilename_ = std::string( save_file_name ); + } + + FileDialogSaveFinished_ = true; } @@ -314,11 +338,16 @@ void UserInterface::NewFrame() navigator.Render(); // handle FileDialog - if (FileDialogFinished_) { - FileDialogFinished_ = false; + if (FileDialogLoadFinished_) { + FileDialogLoadFinished_ = false; FileDialogPending_ = false; Mixer::manager().open(FileDialogFilename_); } + if (FileDialogSaveFinished_) { + FileDialogSaveFinished_ = false; + FileDialogPending_ = false; + Mixer::manager().saveas(FileDialogFilename_); + } } void UserInterface::Render() @@ -389,9 +418,13 @@ void UserInterface::showMenuFile() navigator.hidePannel(); } if (ImGui::MenuItem( ICON_FA_FILE_DOWNLOAD " Save", "Ctrl+S", false, !FileDialogPending_)) { -// UserInterface::manager().OpenFileMedia(); + Mixer::manager().save(); + navigator.hidePannel(); + } + if (ImGui::MenuItem( ICON_FA_FOLDER_OPEN " Save as", NULL, false, !FileDialogPending_)) { + std::thread (FileDialogSave, "./").detach(); + navigator.hidePannel(); } - // TODO : Save As... ImGui::Separator(); if (ImGui::MenuItem( ICON_FA_POWER_OFF " Quit", "Ctrl+Q")) {