]> git.cworth.org Git - vogl/blobdiff - src/vogleditor/vogleditor.cpp
UI: Improved support for shared contexts and viewing shared state objects
[vogl] / src / vogleditor / vogleditor.cpp
index 1b976ca4f4edfba51f030b31d316bac73b3fde7e..c7e0ff9508ead46a52a4e87e9e790bacc4f5c6ee 100644 (file)
 #include "vogl_texture_format.h"
 #include "vogl_trace_file_reader.h"
 #include "vogl_trace_file_writer.h"
-#include "vogleditor_qstatetreemodel.h"
+#include "vogleditor_output.h"
+#include "vogleditor_settings.h"
 #include "vogleditor_statetreetextureitem.h"
 #include "vogleditor_statetreeprogramitem.h"
 #include "vogleditor_statetreeshaderitem.h"
 #include "vogleditor_statetreeframebufferitem.h"
+#include "vogleditor_qstatetreemodel.h"
 #include "vogleditor_qtextureexplorer.h"
 #include "vogleditor_qtrimdialog.h"
 
-#define VOGLEDITOR_DISABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), false);
-#define VOGLEDITOR_ENABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), true);
+#define VOGLEDITOR_DISABLE_STATE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), false);
+#define VOGLEDITOR_ENABLE_STATE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), true);
+
+#define VOGLEDITOR_DISABLE_BOTTOM_TAB(tab) ui->bottomTabWidget->setTabEnabled(ui->bottomTabWidget->indexOf(tab), false);
+#define VOGLEDITOR_ENABLE_BOTTOM_TAB(tab) ui->bottomTabWidget->setTabEnabled(ui->bottomTabWidget->indexOf(tab), true);
 
 //----------------------------------------------------------------------------------------------------------------------
 // globals
 //----------------------------------------------------------------------------------------------------------------------
 static void *g_actual_libgl_module_handle;
 static QString g_PROJECT_NAME = "Vogl Editor";
+static vogleditor_settings g_settings;
+static const char* g_SETTINGS_FILE = "./vogleditor_settings.json";
 
 //----------------------------------------------------------------------------------------------------------------------
 // vogl_get_proc_address_helper
@@ -107,7 +114,6 @@ static bool load_gl()
 VoglEditor::VoglEditor(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::VoglEditor),
-   m_pStatusLabel(NULL),
    m_pFramebufferExplorer(NULL),
    m_pTextureExplorer(NULL),
    m_pRenderbufferExplorer(NULL),
@@ -136,9 +142,17 @@ VoglEditor::VoglEditor(QWidget *parent) :
       vogl_init_actual_gl_entrypoints(vogl_get_proc_address_helper);
    }
 
-   m_pStatusLabel = new QLabel(ui->statusBar);
-   m_pStatusLabel->setBaseSize(150, 12);
-   ui->statusBar->addWidget(m_pStatusLabel, 1);
+   // load the settings file. This will only succeed if the file already exists
+   g_settings.load(g_SETTINGS_FILE);
+
+   // always save/resave the file wiill either be created or so that new settings will be added
+   g_settings.save(g_SETTINGS_FILE);
+
+   this->move(g_settings.window_position_left(), g_settings.window_position_top());
+   this->resize(g_settings.window_size_width(), g_settings.window_size_height());
+
+   vogleditor_output_init(ui->outputTextEdit);
+   vogleditor_output_message("Welcome to VoglEditor!");
 
    // cache the original background color of the search text box
    m_searchTextboxBackgroundColor = ui->searchTextBox->palette().base().color();
@@ -194,105 +208,110 @@ VoglEditor::VoglEditor(QWidget *parent) :
    connect(m_pPlayButton, SIGNAL(clicked()), this, SLOT(playCurrentTraceFile()));
    connect(m_pTrimButton, SIGNAL(clicked()), this, SLOT(trimCurrentTraceFile()));
 
-   connect(m_pProgramExplorer, SIGNAL(program_edited(vogl_program_state*)), this, SLOT(on_program_edited(vogl_program_state*)));
+   connect(m_pProgramExplorer, SIGNAL(program_edited(vogl_program_state*)), this, SLOT(slot_program_edited(vogl_program_state*)));
+
+   connect(m_pVoglReplayProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(slot_readReplayStandardOutput()));
+   connect(m_pVoglReplayProcess, SIGNAL(readyReadStandardError()), this, SLOT(slot_readReplayStandardError()));
 
    reset_tracefile_ui();
 }
 
 VoglEditor::~VoglEditor()
 {
-   close_trace_file();
-   delete ui;
+    // update any settings and save the settings file
+    g_settings.set_window_position_left(this->x());
+    g_settings.set_window_position_top(this->y());
+    g_settings.set_window_size_width(this->width());
+    g_settings.set_window_size_height(this->height());
+    g_settings.save(g_SETTINGS_FILE);
 
-   if (m_pStatusLabel != NULL)
-   {
-       delete m_pStatusLabel;
-       m_pStatusLabel = NULL;
-   }
+    close_trace_file();
+    delete ui;
+    vogleditor_output_deinit();
 
-   if (m_pFramebufferExplorer != NULL)
-   {
-       delete m_pFramebufferExplorer;
-       m_pFramebufferExplorer = NULL;
-   }
+    if (m_pFramebufferExplorer != NULL)
+    {
+        delete m_pFramebufferExplorer;
+        m_pFramebufferExplorer = NULL;
+    }
 
-   if (m_pTextureExplorer != NULL)
-   {
-       delete m_pTextureExplorer;
-       m_pTextureExplorer = NULL;
-   }
+    if (m_pTextureExplorer != NULL)
+    {
+        delete m_pTextureExplorer;
+        m_pTextureExplorer = NULL;
+    }
 
-   if (m_pRenderbufferExplorer != NULL)
-   {
-       delete m_pRenderbufferExplorer;
-       m_pRenderbufferExplorer = NULL;
-   }
+    if (m_pRenderbufferExplorer != NULL)
+    {
+        delete m_pRenderbufferExplorer;
+        m_pRenderbufferExplorer = NULL;
+    }
 
-   if (m_pProgramExplorer != NULL)
-   {
-       delete m_pProgramExplorer;
-       m_pProgramExplorer = NULL;
-   }
+    if (m_pProgramExplorer != NULL)
+    {
+        delete m_pProgramExplorer;
+        m_pProgramExplorer = NULL;
+    }
 
-   if (m_pShaderExplorer != NULL)
-   {
-       delete m_pShaderExplorer;
-       m_pShaderExplorer = NULL;
-   }
+    if (m_pShaderExplorer != NULL)
+    {
+        delete m_pShaderExplorer;
+        m_pShaderExplorer = NULL;
+    }
 
-   if (m_pPlayButton != NULL)
-   {
-       delete m_pPlayButton;
-       m_pPlayButton = NULL;
-   }
+    if (m_pPlayButton != NULL)
+    {
+        delete m_pPlayButton;
+        m_pPlayButton = NULL;
+    }
 
-   if (m_pTrimButton != NULL)
-   {
-       delete m_pTrimButton;
-       m_pTrimButton = NULL;
-   }
+    if (m_pTrimButton != NULL)
+    {
+        delete m_pTrimButton;
+        m_pTrimButton = NULL;
+    }
 
-   if (m_pFramebufferTab_layout != NULL)
-   {
-       delete m_pFramebufferTab_layout;
-       m_pFramebufferTab_layout = NULL;
-   }
+    if (m_pFramebufferTab_layout != NULL)
+    {
+        delete m_pFramebufferTab_layout;
+        m_pFramebufferTab_layout = NULL;
+    }
 
-   if (m_pTextureTab_layout != NULL)
-   {
-       delete m_pTextureTab_layout;
-       m_pTextureTab_layout = NULL;
-   }
+    if (m_pTextureTab_layout != NULL)
+    {
+        delete m_pTextureTab_layout;
+        m_pTextureTab_layout = NULL;
+    }
 
-   if (m_pRenderbufferTab_layout != NULL)
-   {
-       delete m_pRenderbufferTab_layout;
-       m_pRenderbufferTab_layout = NULL;
-   }
+    if (m_pRenderbufferTab_layout != NULL)
+    {
+        delete m_pRenderbufferTab_layout;
+        m_pRenderbufferTab_layout = NULL;
+    }
 
-   if (m_pProgramTab_layout != NULL)
-   {
-       delete m_pProgramTab_layout;
-       m_pProgramTab_layout = NULL;
-   }
+    if (m_pProgramTab_layout != NULL)
+    {
+        delete m_pProgramTab_layout;
+        m_pProgramTab_layout = NULL;
+    }
 
-   if (m_pShaderTab_layout != NULL)
-   {
-       delete m_pShaderTab_layout;
-       m_pShaderTab_layout = NULL;
-   }
+    if (m_pShaderTab_layout != NULL)
+    {
+        delete m_pShaderTab_layout;
+        m_pShaderTab_layout = NULL;
+    }
 
-   if (m_pStateTreeModel != NULL)
-   {
-       delete m_pStateTreeModel;
-       m_pStateTreeModel = NULL;
-   }
+    if (m_pStateTreeModel != NULL)
+    {
+        delete m_pStateTreeModel;
+        m_pStateTreeModel = NULL;
+    }
 
-   if (m_pVoglReplayProcess != NULL)
-   {
-       delete m_pVoglReplayProcess;
-       m_pVoglReplayProcess = NULL;
-   }
+    if (m_pVoglReplayProcess != NULL)
+    {
+        delete m_pVoglReplayProcess;
+        m_pVoglReplayProcess = NULL;
+    }
 }
 
 void VoglEditor::playCurrentTraceFile()
@@ -303,25 +322,18 @@ void VoglEditor::playCurrentTraceFile()
     // update UI
     m_pPlayButton->setEnabled(false);
     m_pTrimButton->setEnabled(false);
-    m_pStatusLabel->clear();
 
-    if (m_traceReplayer.replay(m_pTraceReader, m_pApiCallTreeModel->root(), NULL, 0, true))
-    {
-        // replay was successful
-        m_pPlayButton->setEnabled(true);
-        m_pTrimButton->setEnabled(true);
-    }
-    else
-    {
-        m_pStatusLabel->setText("Failed to replay the trace.");
-    }
+    m_traceReplayer.replay(m_pTraceReader, m_pApiCallTreeModel->root(), NULL, 0, true);
+
+    m_pPlayButton->setEnabled(true);
+    m_pTrimButton->setEnabled(true);
 
     setCursor(origCursor);
 }
 
 void VoglEditor::trimCurrentTraceFile()
 {
-    trim_trace_file(m_openFilename, static_cast<uint>(m_pTraceReader->get_max_frame_index()), 200);
+    trim_trace_file(m_openFilename, static_cast<uint>(m_pTraceReader->get_max_frame_index()), g_settings.trim_large_trace_prompt_size());
 }
 
 /// \return True if the new trim file is now open in the editor
@@ -341,21 +353,41 @@ bool VoglEditor::trim_trace_file(QString filename, uint maxFrameIndex, uint maxA
     arguments << "--trim_frame" << trimDialog.trim_frame() << "--trim_len" << trimDialog.trim_len() << "--trim_file" << trimDialog.trim_file() << filename;
 
 #ifdef __i386__
-    int procRetValue = m_pVoglReplayProcess->execute("./voglreplay32", arguments);
+    QString executable = "./voglreplay32";
 #else
-    int procRetValue = m_pVoglReplayProcess->execute("./voglreplay64", arguments);
+    QString executable = "./voglreplay64";
 #endif
 
+    QString cmdLine = executable + " " + arguments.join(" ");
+
+    vogleditor_output_message("Trimming trace file");
+    vogleditor_output_message(cmdLine.toStdString().c_str());
+    m_pVoglReplayProcess->start(executable, arguments);
+    if (m_pVoglReplayProcess->waitForStarted() == false)
+    {
+        vogleditor_output_error("voglreplay could not be executed.");
+        return false;
+    }
+
+    // This is a bad idea as it will wait forever,
+    // but if the replay is taking forever then we have bigger problems.
+    if(m_pVoglReplayProcess->waitForFinished(-1))
+    {
+        vogleditor_output_message("Trim Completed!");
+    }
+
+    int procRetValue = m_pVoglReplayProcess->exitCode();
+
     bool bCompleted = false;
     if (procRetValue == -2)
     {
         // proc failed to starts
-        m_pStatusLabel->setText("Error: voglreplay could not be executed.");
+        vogleditor_output_error("voglreplay could not be executed.");
     }
     else if (procRetValue == -1)
     {
         // proc crashed
-        m_pStatusLabel->setText("Error: voglreplay aborted unexpectedly.");
+        vogleditor_output_error("voglreplay aborted unexpectedly.");
     }
     else if (procRetValue == 0)
     {
@@ -370,7 +402,7 @@ bool VoglEditor::trim_trace_file(QString filename, uint maxFrameIndex, uint maxA
 
     if (bCompleted)
     {
-        int ret = QMessageBox::warning(this, tr(g_PROJECT_NAME.toStdString().c_str()), tr("Would you like to load the new trimmed trace file?"),
+        int ret = QMessageBox::warning(this, tr("Trim Trace"), tr("Would you like to load the new trimmed trace file?"),
                              QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
 
         if (ret == QMessageBox::Yes)
@@ -378,17 +410,18 @@ bool VoglEditor::trim_trace_file(QString filename, uint maxFrameIndex, uint maxA
             close_trace_file();
             if (open_trace_file(trimDialog.trim_file().toStdString().c_str()))
             {
-                m_pStatusLabel->clear();
                 return true;
             }
             else
             {
+                vogleditor_output_error("Could not open trace file.");
                 QMessageBox::critical(this, tr("Error"), tr("Could not open trace file."));
             }
         }
     }
     else
     {
+        vogleditor_output_error("Failed to trim the trace file.");
         QMessageBox::critical(this, tr("Error"), tr("Failed to trim the trace file."));
     }
     return false;
@@ -402,7 +435,7 @@ void VoglEditor::on_actionE_xit_triggered()
 void VoglEditor::on_action_Open_triggered()
 {
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QString(),
-           tr("GLI Binary Files (*.bin);;JSON Files (*.json)"));
+           tr("VOGL Binary Files (*.bin);;VOGL JSON Files (*.json)"));
 
    if (!fileName.isEmpty()) {
       vogl::dynamic_string filename;
@@ -424,6 +457,8 @@ void VoglEditor::close_trace_file()
 {
    if (m_pTraceReader != NULL)
    {
+      vogleditor_output_message("Closing trace file.");
+      vogleditor_output_message("-------------------");
       m_pTraceReader->close();
       vogl_delete(m_pTraceReader);
       m_pTraceReader = NULL;
@@ -1001,7 +1036,6 @@ vogl_gl_state_snapshot* VoglEditor::read_state_snapshot_from_trace(vogl_trace_fi
    return pSnapshot;
 }
 
-
 bool VoglEditor::open_trace_file(dynamic_string filename)
 {
    QCursor origCursor = this->cursor();
@@ -1013,28 +1047,31 @@ bool VoglEditor::open_trace_file(dynamic_string filename)
 
    dynamic_string actual_keyframe_filename;
 
+   vogleditor_output_message("*********************");
+   vogleditor_output_message("Opening trace file...");
+   vogleditor_output_message(filename.c_str());
+
    vogl_trace_file_reader* tmpReader = vogl_open_trace_file(filename, actual_keyframe_filename, NULL);
 
    if (tmpReader == NULL)
    {
-      m_pStatusLabel->setText("Failed to open: ");
-      m_pStatusLabel->setText(m_pStatusLabel->text().append(filename.c_str()));
+      vogleditor_output_error("Unable to open trace file.");
       this->setCursor(origCursor);
       return false;
    }
    else
    {
-       m_pStatusLabel->clear();
+       vogleditor_output_message("... success!");
    }
 
-   if (tmpReader->get_max_frame_index() > 200)
+   if (tmpReader->get_max_frame_index() > g_settings.trim_large_trace_prompt_size())
    {
        int ret = QMessageBox::warning(this, tr(g_PROJECT_NAME.toStdString().c_str()), tr("The loaded trace file has many frames and debugging may be difficult.\nWould you like to trim the trace?"),
                             QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
 
        if (ret == QMessageBox::Yes)
        {
-           if (trim_trace_file(filename.c_str(), static_cast<uint>(tmpReader->get_max_frame_index()), 200))
+           if (trim_trace_file(filename.c_str(), static_cast<uint>(tmpReader->get_max_frame_index()), g_settings.trim_large_trace_prompt_size()))
            {
                // user decided to open the new trim file, and the UI should already be updated
                // clean up here and return
@@ -1046,6 +1083,7 @@ bool VoglEditor::open_trace_file(dynamic_string filename)
            {
                // either there was an error, or the user decided NOT to open the trim file,
                // so continue to load the original file
+               vogleditor_output_warning("Large trace files may be difficult to debug.");
            }
        }
    }
@@ -1063,8 +1101,7 @@ bool VoglEditor::open_trace_file(dynamic_string filename)
 
    if (ui->treeView->selectionModel() != NULL)
    {
-      //connect(ui->treeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(on_treeView_selectionChanged(const QItemSelection&, const QItemSelection&)));
-      connect(ui->treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(on_treeView_currentChanged(const QModelIndex &, const QModelIndex &)));
+      connect(ui->treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(slot_treeView_currentChanged(const QModelIndex &, const QModelIndex &)));
    }
 
    if (m_pApiCallTreeModel->hasChildren())
@@ -1128,20 +1165,22 @@ bool VoglEditor::open_trace_file(dynamic_string filename)
             }
         }
 
-        QList<int> backtraceSplitterSizes = ui->splitter_3->sizes();
-        int backtraceSplitterTotalSize = backtraceSplitterSizes[0] + backtraceSplitterSizes[1];
-        QList<int> newBacktraceSplitterSizes;
-        if (!bBacktraceVisible)
+        if (bBacktraceVisible)
         {
-            newBacktraceSplitterSizes.append(backtraceSplitterTotalSize);
-            newBacktraceSplitterSizes.append(0);
-            ui->splitter_3->setSizes(newBacktraceSplitterSizes);
+            if (ui->bottomTabWidget->indexOf(ui->callStackTab) == -1)
+            {
+                // unhide the tab
+                ui->bottomTabWidget->insertTab(1, ui->callStackTab, "Call Stack");
+                VOGLEDITOR_ENABLE_BOTTOM_TAB(ui->callStackTab);
+            }
+            else
+            {
+                VOGLEDITOR_ENABLE_BOTTOM_TAB(ui->callStackTab);
+            }
         }
         else
         {
-            newBacktraceSplitterSizes << (backtraceSplitterTotalSize * 0.75)
-                                      << (backtraceSplitterTotalSize * 0.25);
-            ui->splitter_3->setSizes(newBacktraceSplitterSizes);
+            ui->bottomTabWidget->removeTab(ui->bottomTabWidget->indexOf(ui->callStackTab));
         }
 
         // machine info
@@ -1253,6 +1292,10 @@ void VoglEditor::displayMachineInfoHelper(QString prefix, const QString& section
 void VoglEditor::displayMachineInfo()
 {
     VOGL_ASSERT(m_pTraceReader != NULL);
+    if (m_pTraceReader == NULL)
+    {
+        return;
+    }
 
     bool bMachineInfoVisible = false;
     if (m_pTraceReader->get_archive_blob_manager().does_exist(VOGL_TRACE_ARCHIVE_MACHINE_INFO_FILENAME))
@@ -1283,19 +1326,20 @@ void VoglEditor::displayMachineInfo()
 
     if (bMachineInfoVisible)
     {
-        if (ui->tabWidget->indexOf(ui->machineInfoTab) == -1)
+        if (ui->bottomTabWidget->indexOf(ui->machineInfoTab) == -1)
         {
             // unhide the tab
-            ui->tabWidget->insertTab(0, ui->machineInfoTab, "Machine Info");
+            ui->bottomTabWidget->insertTab(1, ui->machineInfoTab, "Machine Info");
+            VOGLEDITOR_ENABLE_BOTTOM_TAB(ui->machineInfoTab);
         }
         else
         {
-            VOGLEDITOR_ENABLE_TAB(ui->machineInfoTab);
+            VOGLEDITOR_ENABLE_BOTTOM_TAB(ui->machineInfoTab);
         }
     }
     else
     {
-        ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->machineInfoTab));
+        ui->bottomTabWidget->removeTab(ui->bottomTabWidget->indexOf(ui->machineInfoTab));
     }
 }
 
@@ -1314,11 +1358,11 @@ void VoglEditor::reset_tracefile_ui()
     ui->searchPrevButton->setEnabled(false);
     ui->searchNextButton->setEnabled(false);
 
-    m_pStatusLabel->clear();
     m_pPlayButton->setEnabled(false);
     m_pTrimButton->setEnabled(false);
 
-    VOGLEDITOR_DISABLE_TAB(ui->machineInfoTab);
+    VOGLEDITOR_DISABLE_BOTTOM_TAB(ui->machineInfoTab);
+    VOGLEDITOR_DISABLE_BOTTOM_TAB(ui->callStackTab);
 
     reset_snapshot_ui();
 }
@@ -1337,12 +1381,12 @@ void VoglEditor::reset_snapshot_ui()
 
     QWidget* pCurrentTab = ui->tabWidget->currentWidget();
 
-    VOGLEDITOR_DISABLE_TAB(ui->stateTab);
-    VOGLEDITOR_DISABLE_TAB(ui->framebufferTab);
-    VOGLEDITOR_DISABLE_TAB(ui->programTab);
-    VOGLEDITOR_DISABLE_TAB(ui->shaderTab);
-    VOGLEDITOR_DISABLE_TAB(ui->textureTab);
-    VOGLEDITOR_DISABLE_TAB(ui->renderbufferTab);
+    VOGLEDITOR_DISABLE_STATE_TAB(ui->stateTab);
+    VOGLEDITOR_DISABLE_STATE_TAB(ui->framebufferTab);
+    VOGLEDITOR_DISABLE_STATE_TAB(ui->programTab);
+    VOGLEDITOR_DISABLE_STATE_TAB(ui->shaderTab);
+    VOGLEDITOR_DISABLE_STATE_TAB(ui->textureTab);
+    VOGLEDITOR_DISABLE_STATE_TAB(ui->renderbufferTab);
 
     ui->tabWidget->setCurrentWidget(pCurrentTab);
 }
@@ -1452,7 +1496,7 @@ void VoglEditor::update_ui_for_snapshot(vogleditor_gl_state_snapshot* pStateSnap
    ui->stateTreeView->expandToDepth(1);
    ui->stateTreeView->setColumnWidth(0, ui->stateTreeView->width() * 0.5);
 
-   VOGLEDITOR_ENABLE_TAB(ui->stateTab);
+   VOGLEDITOR_ENABLE_STATE_TAB(ui->stateTab);
 
    if (pStateSnapshot->get_contexts().size() > 0)
    {
@@ -1460,67 +1504,104 @@ void VoglEditor::update_ui_for_snapshot(vogleditor_gl_state_snapshot* pStateSnap
        if (curContextHandle != 0)
        {
            vogl_context_snapshot* pContext = pStateSnapshot->get_context(curContextHandle);
-
-           // textures
-           vogl_gl_object_state_ptr_vec textureObjects;
-           pContext->get_all_objects_of_category(cGLSTTexture, textureObjects);
-           m_pTextureExplorer->set_texture_objects(textureObjects);
-
-           GLuint curActiveTextureUnit = pContext->get_general_state().get_value<GLuint>(GL_ACTIVE_TEXTURE);
-           if (curActiveTextureUnit >= GL_TEXTURE0 && curActiveTextureUnit < (GL_TEXTURE0 + pContext->get_context_info().get_max_texture_image_units()))
-           {
-               GLuint cur2DBinding = pContext->get_general_state().get_value<GLuint>(GL_TEXTURE_2D_BINDING_EXT, curActiveTextureUnit - GL_TEXTURE0);
-               displayTexture(cur2DBinding, false);
-           }
-
-           // renderbuffers
-           vogl_gl_object_state_ptr_vec renderbufferObjects;
-           pContext->get_all_objects_of_category(cGLSTRenderbuffer, renderbufferObjects);
-           m_pRenderbufferExplorer->set_texture_objects(renderbufferObjects);
-           if (renderbufferObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->renderbufferTab); }
-
-           // framebuffer
-           vogl_gl_object_state_ptr_vec framebufferObjects;
-           pContext->get_all_objects_of_category(cGLSTFramebuffer, framebufferObjects);
-           m_pFramebufferExplorer->set_framebuffer_objects(framebufferObjects, *pContext, pStateSnapshot->get_default_framebuffer());
-           GLuint64 curDrawFramebuffer = pContext->get_general_state().get_value<GLuint64>(GL_DRAW_FRAMEBUFFER_BINDING);
-           displayFramebuffer(curDrawFramebuffer, false);
-
-           // programs
-           vogl_gl_object_state_ptr_vec programObjects;
-           pContext->get_all_objects_of_category(cGLSTProgram, programObjects);
-           m_pProgramExplorer->set_program_objects(programObjects);
-           GLuint64 curProgram = pContext->get_general_state().get_value<GLuint64>(GL_CURRENT_PROGRAM);
-           m_pProgramExplorer->set_active_program(curProgram);
-           if (programObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->programTab); }
-
-           // shaders
-           vogl_gl_object_state_ptr_vec shaderObjects;
-           pContext->get_all_objects_of_category(cGLSTShader, shaderObjects);
-           m_pShaderExplorer->set_shader_objects(shaderObjects);
-           if (curProgram != 0)
-           {
-               for (vogl_gl_object_state_ptr_vec::iterator iter = programObjects.begin(); iter != programObjects.end(); iter++)
-               {
-                   if ((*iter)->get_snapshot_handle() == curProgram)
-                   {
-                       vogl_program_state* pProgramState = static_cast<vogl_program_state*>(*iter);
-                       if (pProgramState->get_attached_shaders().size() > 0)
-                       {
-                           uint curShader = pProgramState->get_attached_shaders()[0];
-                           m_pShaderExplorer->set_active_shader(curShader);
-                       }
-                       break;
-                   }
-               }
-           }
-           if (shaderObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->shaderTab); }
+           update_ui_for_context(pContext, pStateSnapshot);
        }
    }
 
    this->setCursor(origCursor);
 }
 
+void VoglEditor::update_ui_for_context(vogl_context_snapshot* pContext, vogleditor_gl_state_snapshot* pStateSnapshot)
+{
+    // vogl stores all the created objects in the deepest context, so need to find that context to populate the UI
+    vogl::vector<vogl_context_snapshot*> sharingContexts;
+    sharingContexts.push_back(pContext);
+    vogl_context_snapshot* pRootContext = pContext;
+    vogl_context_snapshot* pTmpContext = NULL;
+    while (pRootContext->get_context_desc().get_trace_share_context() != 0)
+    {
+        pTmpContext = pStateSnapshot->get_context(pRootContext->get_context_desc().get_trace_share_context());
+        VOGL_ASSERT(pTmpContext != NULL);
+        if (pTmpContext == NULL)
+        {
+            // this is a bug
+            break;
+        }
+
+        // update the root context
+        pRootContext = pTmpContext;
+    }
+
+    // add the root context if it is new (ie, not equal the supplied context)
+    if (pRootContext != pContext)
+    {
+        sharingContexts.push_back(pRootContext);
+    }
+
+    // textures
+    m_pTextureExplorer->clear();
+    uint textureCount = m_pTextureExplorer->set_texture_objects(sharingContexts);
+
+    GLuint curActiveTextureUnit = pContext->get_general_state().get_value<GLuint>(GL_ACTIVE_TEXTURE);
+    if (curActiveTextureUnit >= GL_TEXTURE0 && curActiveTextureUnit < (GL_TEXTURE0 + pContext->get_context_info().get_max_texture_image_units()))
+    {
+        GLuint cur2DBinding = pContext->get_general_state().get_value<GLuint>(GL_TEXTURE_2D_BINDING_EXT, curActiveTextureUnit - GL_TEXTURE0);
+        displayTexture(cur2DBinding, false);
+    }
+    if (textureCount > 0) { VOGLEDITOR_ENABLE_STATE_TAB(ui->textureTab); }
+
+    // renderbuffers
+    m_pRenderbufferExplorer->clear();
+    int renderbufferCount = m_pRenderbufferExplorer->set_renderbuffer_objects(sharingContexts);
+    if (renderbufferCount > 0) { VOGLEDITOR_ENABLE_STATE_TAB(ui->renderbufferTab); }
+
+    // framebuffer
+    m_pFramebufferExplorer->clear();
+    uint framebufferCount = m_pFramebufferExplorer->set_framebuffer_objects(pContext, sharingContexts, &(pStateSnapshot->get_default_framebuffer()));
+    GLuint64 curDrawFramebuffer = pContext->get_general_state().get_value<GLuint64>(GL_DRAW_FRAMEBUFFER_BINDING);
+    displayFramebuffer(curDrawFramebuffer, false);
+    if (framebufferCount > 0) { VOGLEDITOR_ENABLE_STATE_TAB(ui->framebufferTab); }
+
+    // programs
+    m_pProgramExplorer->clear();
+    uint programCount = m_pProgramExplorer->set_program_objects(sharingContexts);
+    GLuint64 curProgram = pContext->get_general_state().get_value<GLuint64>(GL_CURRENT_PROGRAM);
+    m_pProgramExplorer->set_active_program(curProgram);
+    if (programCount > 0) { VOGLEDITOR_ENABLE_STATE_TAB(ui->programTab); }
+
+    // shaders
+    m_pShaderExplorer->clear();
+    uint shaderCount = m_pShaderExplorer->set_shader_objects(sharingContexts);
+    if (curProgram != 0)
+    {
+        bool bFound = false;
+        for (uint c = 0; c < sharingContexts.size(); c++)
+        {
+            vogl_gl_object_state_ptr_vec programObjects;
+            sharingContexts[c]->get_all_objects_of_category(cGLSTProgram, programObjects);
+            for (vogl_gl_object_state_ptr_vec::iterator iter = programObjects.begin(); iter != programObjects.end(); iter++)
+            {
+                if ((*iter)->get_snapshot_handle() == curProgram)
+                {
+                    vogl_program_state* pProgramState = static_cast<vogl_program_state*>(*iter);
+                    if (pProgramState->get_attached_shaders().size() > 0)
+                    {
+                        uint curShader = pProgramState->get_attached_shaders()[0];
+                        m_pShaderExplorer->set_active_shader(curShader);
+                    }
+
+                    bFound = true;
+                    break;
+                }
+            }
+
+            if (bFound)
+                break;
+        }
+    }
+    if (shaderCount > 0) { VOGLEDITOR_ENABLE_STATE_TAB(ui->shaderTab); }
+}
+
 void VoglEditor::on_stateTreeView_clicked(const QModelIndex &index)
 {
    vogleditor_stateTreeItem* pStateItem = static_cast<vogleditor_stateTreeItem*>(index.internalPointer());
@@ -1617,7 +1698,7 @@ void VoglEditor::displayFramebuffer(GLuint64 framebufferHandle, bool bBringTabTo
 
     if (bDisplayedFBO)
     {
-        VOGLEDITOR_ENABLE_TAB(ui->framebufferTab);
+        VOGLEDITOR_ENABLE_STATE_TAB(ui->framebufferTab);
         if (bBringTabToFront)
         {
             ui->tabWidget->setCurrentWidget(ui->framebufferTab);
@@ -1631,7 +1712,7 @@ bool VoglEditor::displayTexture(GLuint64 textureHandle, bool bBringTabToFront)
 
     if (bDisplayedTexture)
     {
-        VOGLEDITOR_ENABLE_TAB(ui->textureTab);
+        VOGLEDITOR_ENABLE_STATE_TAB(ui->textureTab);
         if (bBringTabToFront)
         {
             ui->tabWidget->setCurrentWidget(ui->textureTab);
@@ -1641,7 +1722,7 @@ bool VoglEditor::displayTexture(GLuint64 textureHandle, bool bBringTabToFront)
     return bDisplayedTexture;
 }
 
-void VoglEditor::on_treeView_currentChanged(const QModelIndex & current, const QModelIndex & previous)
+void VoglEditor::slot_treeView_currentChanged(const QModelIndex & current, const QModelIndex & previous)
 {
     VOGL_NOTE_UNUSED(previous);
     onApiCallSelected(current, false);
@@ -1842,7 +1923,7 @@ void VoglEditor::on_nextDrawcallButton_clicked()
     }
 }
 
-void VoglEditor::on_program_edited(vogl_program_state* pNewProgramState)
+void VoglEditor::slot_program_edited(vogl_program_state* pNewProgramState)
 {
     VOGL_NOTE_UNUSED(pNewProgramState);
 
@@ -1888,8 +1969,11 @@ void VoglEditor::recursive_update_snapshot_flags(vogleditor_apiCallTreeItem* pIt
     }
 }
 
-#undef VOGLEDITOR_DISABLE_TAB
-#undef VOGLEDITOR_ENABLE_TAB
+#undef VOGLEDITOR_DISABLE_STATE_TAB
+#undef VOGLEDITOR_ENABLE_STATE_TAB
+
+#undef VOGLEDITOR_DISABLE_BOTTOM_TAB
+#undef VOGLEDITOR_ENABLE_BOTTOM_TAB
 
 void VoglEditor::on_actionSave_Session_triggered()
 {
@@ -1907,7 +1991,7 @@ void VoglEditor::on_actionSave_Session_triggered()
 
     if (!save_session_to_disk(sessionFilename))
     {
-        m_pStatusLabel->setText("ERROR: Failed to save session");
+        vogleditor_output_error("Failed to save session.");
     }
 }
 
@@ -1920,7 +2004,7 @@ void VoglEditor::on_actionOpen_Session_triggered()
 
     if (!load_session_from_disk(sessionFilename))
     {
-        m_pStatusLabel->setText("ERROR: Failed to load session");
+        vogleditor_output_error("Failed to load session.");
     }
 
     setCursor(origCursor);
@@ -1945,3 +2029,31 @@ void VoglEditor::on_searchTextBox_returnPressed()
         }
     }
 }
+
+void VoglEditor::slot_readReplayStandardOutput()
+{
+    m_pVoglReplayProcess->setReadChannel(QProcess::StandardOutput);
+    while (m_pVoglReplayProcess->canReadLine())
+    {
+        QByteArray output = m_pVoglReplayProcess->readLine();
+        if (output.endsWith("\n"))
+        {
+            output.remove(output.size() - 1, 1);
+        }
+        vogleditor_output_message(output.constData());
+    }
+}
+
+void VoglEditor::slot_readReplayStandardError()
+{
+    m_pVoglReplayProcess->setReadChannel(QProcess::StandardError);
+    while (m_pVoglReplayProcess->canReadLine())
+    {
+        QByteArray output = m_pVoglReplayProcess->readLine();
+        if (output.endsWith("\n"))
+        {
+            output.remove(output.size() - 1, 1);
+        }
+        vogleditor_output_error(output.constData());
+    }
+}