]> git.cworth.org Git - vogl/blob - src/vogleditor/vogleditor.cpp
UI: Remove m_pTraceWriter since it was not being used and was causing an unnecessary...
[vogl] / src / vogleditor / vogleditor.cpp
1 /**************************************************************************
2  *
3  * Copyright 2013-2014 RAD Game Tools and Valve Software
4  * All Rights Reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  *
24  **************************************************************************/
25
26 #include <QFileDialog>
27 #include <QHBoxLayout>
28 #include <QItemSelection>
29 #include <QPalette>
30 #include <QSortFilterProxyModel>
31 #include <QSpacerItem>
32 #include <QToolButton>
33 #include <QMessageBox>
34
35 #include "ui_vogleditor.h"
36 #include "vogleditor.h"
37
38 #include "vogleditor_qapicalltreemodel.h"
39 #include "vogleditor_apicalltimelinemodel.h"
40
41 #include "vogl_platform.h"
42 #include "vogl_assert.h"
43 #include "vogl_file_utils.h"
44 #include "vogl_find_files.h"
45
46 #include "vogl_texture_format.h"
47 #include "vogl_trace_file_reader.h"
48 #include "vogl_trace_file_writer.h"
49 #include "vogleditor_qstatetreemodel.h"
50 #include "vogleditor_statetreetextureitem.h"
51 #include "vogleditor_statetreeprogramitem.h"
52 #include "vogleditor_statetreeshaderitem.h"
53 #include "vogleditor_statetreeframebufferitem.h"
54 #include "vogleditor_qtextureexplorer.h"
55
56 #define VOGLEDITOR_DISABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), false);
57 #define VOGLEDITOR_ENABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), true);
58
59 //----------------------------------------------------------------------------------------------------------------------
60 // globals
61 //----------------------------------------------------------------------------------------------------------------------
62 static void *g_actual_libgl_module_handle;
63 static QString g_PROJECT_NAME = "Vogl Editor";
64
65 //----------------------------------------------------------------------------------------------------------------------
66 // vogl_get_proc_address_helper
67 //----------------------------------------------------------------------------------------------------------------------
68 static vogl_void_func_ptr_t vogl_get_proc_address_helper(const char *pName)
69 {
70    VOGL_FUNC_TRACER
71
72    vogl_void_func_ptr_t pFunc = g_actual_libgl_module_handle ? reinterpret_cast<vogl_void_func_ptr_t>(dlsym(g_actual_libgl_module_handle, pName)) : NULL;
73
74    if ((!pFunc) && (GL_ENTRYPOINT(glXGetProcAddress)))
75       pFunc = reinterpret_cast<vogl_void_func_ptr_t>( GL_ENTRYPOINT(glXGetProcAddress)(reinterpret_cast<const GLubyte*>(pName)) );
76
77    return pFunc;
78 }
79
80
81 //----------------------------------------------------------------------------------------------------------------------
82 // load_gl
83 //----------------------------------------------------------------------------------------------------------------------
84 static bool load_gl()
85 {
86    VOGL_FUNC_TRACER
87
88    g_actual_libgl_module_handle = dlopen("libGL.so.1", RTLD_LAZY);
89    if (!g_actual_libgl_module_handle)
90    {
91       vogl_error_printf("%s: Failed loading libGL.so.1!\n", VOGL_FUNCTION_NAME);
92       return false;
93    }
94
95    GL_ENTRYPOINT(glXGetProcAddress) = reinterpret_cast<glXGetProcAddress_func_ptr_t>(dlsym(g_actual_libgl_module_handle, "glXGetProcAddress"));
96    if (!GL_ENTRYPOINT(glXGetProcAddress))
97    {
98       vogl_error_printf("%s: Failed getting address of glXGetProcAddress() from libGL.so.1!\n", VOGL_FUNCTION_NAME);
99       return false;
100    }
101
102    return true;
103 }
104
105 VoglEditor::VoglEditor(QWidget *parent) :
106    QMainWindow(parent),
107    ui(new Ui::VoglEditor),
108    m_statusLabel(NULL),
109    m_framebufferExplorer(NULL),
110    m_textureExplorer(NULL),
111    m_renderbufferExplorer(NULL),
112    m_programExplorer(NULL),
113    m_shaderExplorer(NULL),
114    m_timeline(NULL),
115    m_currentSnapshot(NULL),
116    m_pCurrentCallTreeItem(NULL),
117    m_pPlayButton(NULL),
118    m_pPauseButton(NULL),
119    m_pTrimButton(NULL),
120    m_pStopButton(NULL),
121    m_pTraceReader(NULL),
122    m_pApicallTreeModel(NULL)
123 {
124    ui->setupUi(this);
125
126    if (load_gl())
127    {
128       vogl_init_actual_gl_entrypoints(vogl_get_proc_address_helper);
129    }
130
131    m_statusLabel = new QLabel(ui->statusBar);
132    m_statusLabel->setBaseSize(150, 12);
133    ui->statusBar->addWidget(m_statusLabel, 1);
134
135    // setup framebuffer tab
136    QGridLayout* framebufferTab_layout = new QGridLayout;
137    m_framebufferExplorer = new vogleditor_QFramebufferExplorer(ui->framebufferTab);
138    framebufferTab_layout->addWidget(m_framebufferExplorer, 0, 0);
139    ui->framebufferTab->setLayout(framebufferTab_layout);
140
141    // setup texture tab
142    QGridLayout* textureTab_layout = new QGridLayout;
143    m_textureExplorer = new vogleditor_QTextureExplorer(ui->textureTab);
144    textureTab_layout->addWidget(m_textureExplorer, 0, 0);
145    ui->textureTab->setLayout(textureTab_layout);
146
147    // setup renderbuffer tab
148    QGridLayout* rbTab_layout = new QGridLayout;
149    m_renderbufferExplorer = new vogleditor_QTextureExplorer(ui->renderbufferTab);
150    rbTab_layout->addWidget(m_renderbufferExplorer, 0, 0);
151    ui->renderbufferTab->setLayout(rbTab_layout);
152
153    // setup program tab
154    QGridLayout* programTab_layout = new QGridLayout;
155    m_programExplorer = new vogleditor_QProgramExplorer(ui->programTab);
156    programTab_layout->addWidget(m_programExplorer, 0, 0);
157    ui->programTab->setLayout(programTab_layout);
158
159    // setup shader tab
160    QGridLayout* shaderTab_layout = new QGridLayout;
161    m_shaderExplorer = new vogleditor_QShaderExplorer(ui->shaderTab);
162    shaderTab_layout->addWidget(m_shaderExplorer, 0, 0);
163    ui->shaderTab->setLayout(shaderTab_layout);
164
165    // setup timeline
166    m_timeline = new vogleditor_QTimelineView();
167    m_timeline->setMinimumHeight(100);
168    ui->verticalLayout->addWidget(m_timeline);
169    ui->verticalLayout->removeWidget(ui->timelineViewPlaceholder);
170
171    // add buttons to toolbar
172    m_pPlayButton = new QToolButton(ui->mainToolBar);
173    m_pPlayButton->setText("Play trace");
174    m_pPlayButton->setEnabled(false);
175
176    m_pPauseButton = new QToolButton(ui->mainToolBar);
177    m_pPauseButton->setText("Pause");
178    m_pPauseButton->setEnabled(false);
179
180    m_pTrimButton = new QToolButton(ui->mainToolBar);
181    m_pTrimButton->setText("Trim");
182    m_pTrimButton->setEnabled(false);
183
184    m_pStopButton = new QToolButton(ui->mainToolBar);
185    m_pStopButton->setText("Stop");
186    m_pStopButton->setEnabled(false);
187
188    // Temporarily hide the other buttons (until asyncronous playback is supported)
189    m_pPauseButton->setVisible(false);
190    m_pTrimButton->setVisible(false);
191    m_pStopButton->setVisible(false);
192
193    ui->mainToolBar->addWidget(m_pPlayButton);
194    ui->mainToolBar->addWidget(m_pPauseButton);
195    ui->mainToolBar->addWidget(m_pTrimButton);
196    ui->mainToolBar->addWidget(m_pStopButton);
197
198    connect(m_pPlayButton, SIGNAL(clicked()), this, SLOT(playCurrentTraceFile()));
199    connect(m_pPauseButton, SIGNAL(clicked()), this, SLOT(pauseCurrentTraceFile()));
200    connect(m_pTrimButton, SIGNAL(clicked()), this, SLOT(trimCurrentTraceFile()));
201    connect(m_pStopButton, SIGNAL(clicked()), this, SLOT(stopCurrentTraceFile()));
202
203    connect(m_programExplorer, SIGNAL(program_edited(vogl_program_state*)), this, SLOT(on_program_edited(vogl_program_state*)));
204
205    reset_tracefile_ui();
206 }
207
208 VoglEditor::~VoglEditor()
209 {
210    close_trace_file();
211    delete ui;
212
213    if (m_textureExplorer != NULL)
214    {
215        delete m_textureExplorer;
216        m_textureExplorer = NULL;
217    }
218
219    if (m_renderbufferExplorer != NULL)
220    {
221        delete m_renderbufferExplorer;
222        m_renderbufferExplorer = NULL;
223    }
224
225    if (m_programExplorer != NULL)
226    {
227        delete m_programExplorer;
228        m_programExplorer = NULL;
229    }
230
231    if (m_shaderExplorer != NULL)
232    {
233        delete m_shaderExplorer;
234        m_shaderExplorer = NULL;
235    }
236 }
237
238 void VoglEditor::playCurrentTraceFile()
239 {
240     QCursor origCursor = cursor();
241     setCursor(Qt::WaitCursor);
242
243     // update UI
244     m_pPlayButton->setEnabled(false);
245     m_pPauseButton->setEnabled(true);
246     m_pTrimButton->setEnabled(true);
247     m_pStopButton->setEnabled(true);
248     m_statusLabel->clear();
249
250     if (m_traceReplayer.replay(m_pTraceReader, m_pApicallTreeModel->root(), NULL, 0, true))
251     {
252         // replay was successful
253         m_pPlayButton->setEnabled(true);
254         m_pPauseButton->setEnabled(false);
255         m_pTrimButton->setEnabled(false);
256         m_pStopButton->setEnabled(false);
257     }
258     else
259     {
260         m_statusLabel->setText("Failed to replay the trace.");
261     }
262
263     setCursor(origCursor);
264 }
265
266 void VoglEditor::pauseCurrentTraceFile()
267 {
268     if (m_traceReplayer.pause())
269     {
270        // update UI
271        m_pPlayButton->setEnabled(true);
272        m_pPauseButton->setEnabled(false);
273        m_pTrimButton->setEnabled(true);
274        m_pStopButton->setEnabled(true);
275        m_statusLabel->clear();
276     }
277     else
278     {
279         m_statusLabel->setText("Failed to pause the trace replay.");
280     }
281 }
282
283 void VoglEditor::trimCurrentTraceFile()
284 {
285     if (m_traceReplayer.trim())
286     {
287         m_statusLabel->clear();
288     }
289     else
290     {
291         m_statusLabel->setText("Failed to trim the trace replay.");
292     }
293 }
294
295 void VoglEditor::stopCurrentTraceFile()
296 {
297     if (m_traceReplayer.stop())
298     {
299         // update UI
300         m_pPlayButton->setEnabled(true);
301         m_pPauseButton->setEnabled(false);
302         m_pTrimButton->setEnabled(false);
303         m_pStopButton->setEnabled(false);
304         m_statusLabel->clear();
305     }
306     else
307     {
308         m_statusLabel->setText("Failed to stop the trace replay.");
309     }
310 }
311
312 void VoglEditor::on_actionE_xit_triggered()
313 {
314    qApp->quit();
315 }
316
317 void VoglEditor::on_action_Open_triggered()
318 {
319    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QString(),
320            tr("GLI Binary Files (*.bin);;JSON Files (*.json)"));
321
322    if (!fileName.isEmpty()) {
323       vogl::dynamic_string filename;
324       filename.set(fileName.toStdString().c_str());
325
326       if (open_trace_file(filename) == false) {
327           QMessageBox::critical(this, tr("Error"), tr("Could not open file"));
328           return;
329       }
330    }
331 }
332
333 void VoglEditor::on_action_Close_triggered()
334 {
335     close_trace_file();
336 }
337
338 void VoglEditor::close_trace_file()
339 {
340    if (m_pTraceReader != NULL)
341    {
342       m_pTraceReader->close();
343       vogl_delete(m_pTraceReader);
344       m_pTraceReader = NULL;
345
346       setWindowTitle(g_PROJECT_NAME);
347
348       m_openFilename.clear();
349       m_backtraceToJsonMap.clear();
350       m_backtraceDoc.clear();
351       m_searchApicallResults.clear();
352
353       reset_tracefile_ui();
354
355       ui->treeView->setModel(NULL);
356       ui->machineInfoText->clear();
357       ui->backtraceText->clear();
358       m_timeline->setModel(NULL);
359       m_timeline->repaint();
360
361       if (m_pTimelineModel != NULL)
362       {
363           delete m_pTimelineModel;
364           m_pTimelineModel = NULL;
365       }
366    }
367 }
368
369 void VoglEditor::write_child_api_calls(vogleditor_apiCallTreeItem* pItem, FILE* pFile)
370 {
371     QString string = pItem->columnData(VOGL_ACTC_APICALL, Qt::DisplayRole).toString();
372     vogl_fwrite(string.toStdString().c_str(), 1, string.size(), pFile);
373     vogl_fwrite("\r\n", 1, 2, pFile);
374
375     for (int i = 0; i < pItem->childCount(); i++)
376     {
377         write_child_api_calls(pItem->child(i), pFile);
378     }
379 }
380
381 void VoglEditor::on_actionExport_API_Calls_triggered()
382 {
383     QString suggestedName = m_openFilename;
384
385     int lastIndex = suggestedName.lastIndexOf('-');
386     if (lastIndex != -1)
387     {
388         suggestedName = suggestedName.remove(lastIndex, suggestedName.size() - lastIndex);
389     }
390     suggestedName += "-ApiCalls.txt";
391
392     QString fileName = QFileDialog::getSaveFileName(this, tr("Export API Calls"), suggestedName, tr("Text (*.txt)"));
393
394     if (!fileName.isEmpty())
395     {
396         vogl::dynamic_string filename;
397         filename.set(fileName.toStdString().c_str());
398
399         FILE* pFile = vogl_fopen(filename.c_str(), "w");
400         vogleditor_QApiCallTreeModel* pModel = static_cast<vogleditor_QApiCallTreeModel*>(ui->treeView->model());
401         vogleditor_apiCallTreeItem* pRoot = pModel->root();
402         for (int i = 0; i < pRoot->childCount(); i++)
403         {
404             write_child_api_calls(pRoot->child(i), pFile);
405         }
406         vogl_fclose(pFile);
407     }
408 }
409
410 static const unsigned int VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1 = 1;
411 static const unsigned int VOGLEDITOR_SESSION_FILE_FORMAT_VERSION = VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1;
412
413 bool VoglEditor::load_session_from_disk(QString sessionFile)
414 {
415     // open the json doc
416     json_document sessionDoc;
417     if (!sessionDoc.deserialize_file(sessionFile.toStdString().c_str()))
418     {
419         return false;
420     }
421
422     // look for expected metadata
423     json_node* pMetadata = sessionDoc.get_root()->find_child_object("metadata");
424     if (pMetadata == NULL)
425     {
426         return false;
427     }
428
429     const json_value& rFormatVersion = pMetadata->find_value("session_file_format_version");
430     if (!rFormatVersion.is_valid())
431     {
432         return false;
433     }
434
435     if (rFormatVersion.as_uint32() != VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1)
436     {
437         return false;
438     }
439
440     // load base trace file
441     json_node* pBaseTraceFile = sessionDoc.get_root()->find_child_object("base_trace_file");
442     if (pBaseTraceFile == NULL)
443     {
444         return false;
445     }
446
447     const json_value& rBaseTraceFilePath = pBaseTraceFile->find_value("rel_path");
448     const json_value& rBaseTraceFileUuid = pBaseTraceFile->find_value("uuid");
449
450     if (!rBaseTraceFilePath.is_valid() || !rBaseTraceFileUuid.is_valid())
451     {
452         return false;
453     }
454
455     dynamic_string sessionPathName;
456     dynamic_string sessionFileName;
457     file_utils::split_path(sessionFile.toStdString().c_str(), sessionPathName, sessionFileName);
458
459     dynamic_string traceFilePath = sessionPathName;
460     traceFilePath.append(rBaseTraceFilePath.as_string());
461
462     if (!open_trace_file(traceFilePath))
463     {
464         return false;
465     }
466
467     // TODO: verify UUID of the loaded trace file
468
469     // load session data if it is available
470     json_node* pSessionData = sessionDoc.get_root()->find_child_object("session_data");
471     if (pSessionData != NULL)
472     {
473         const json_value& rSessionPath = pSessionData->find_value("rel_path");
474         if (!rSessionPath.is_valid())
475         {
476             return false;
477         }
478
479         dynamic_string sessionDataPath = sessionPathName;
480         sessionDataPath.append(rSessionPath.as_string());
481
482         vogl_loose_file_blob_manager file_blob_manager;
483         file_blob_manager.init(cBMFReadWrite, sessionDataPath.c_str());
484         vogl_blob_manager* pBlob_manager = static_cast<vogl_blob_manager*>(&file_blob_manager);
485
486         // load snapshots
487         const json_node* pSnapshots = pSessionData->find_child_array("snapshots");
488         for (unsigned int i = 0; i < pSnapshots->size(); i++)
489         {
490             const json_node* pSnapshotNode = pSnapshots->get_value_as_object(i);
491
492             const json_value& uuid = pSnapshotNode->find_value("uuid");
493             const json_value& isValid = pSnapshotNode->find_value("is_valid");
494             const json_value& isEdited = pSnapshotNode->find_value("is_edited");
495             const json_value& isOutdated = pSnapshotNode->find_value("is_outdated");
496             const json_value& frameNumber = pSnapshotNode->find_value("frame_number");
497             const json_value& callIndex = pSnapshotNode->find_value("call_index");
498             const json_value& path = pSnapshotNode->find_value("rel_path");
499
500             // make sure expected nodes are valid
501             if (!isValid.is_valid() || !isEdited.is_valid() || !isOutdated.is_valid())
502             {
503                 return false;
504             }
505
506             vogl_gl_state_snapshot* pSnapshot = NULL;
507
508             if (path.is_valid() && isValid.as_bool() && uuid.is_valid())
509             {
510                 dynamic_string snapshotPath = sessionDataPath;
511                 snapshotPath.append(path.as_string());
512
513                 // load the snapshot
514                 json_document snapshotDoc;
515                 if (!snapshotDoc.deserialize_file(snapshotPath.c_str()))
516                 {
517                     return false;
518                 }
519
520                 // attempt to verify the snapshot file
521                 json_node* pSnapshotRoot = snapshotDoc.get_root();
522                 if (pSnapshotRoot == NULL)
523                 {
524                     vogl_warning_printf("Invalid snapshot file at %s.", path.as_string_ptr());
525                     continue;
526                 }
527
528                 const json_value& snapshotUuid = pSnapshotRoot->find_value("uuid");
529                 if (!snapshotUuid.is_valid())
530                 {
531                     vogl_warning_printf("Invalid 'uuid' in snapshot file at %s.", path.as_string_ptr());
532                     continue;
533                 }
534
535                 if (snapshotUuid.as_string() != uuid.as_string())
536                 {
537                     vogl_warning_printf("Mismatching 'uuid' between snapshot file at %s and that stored in the session file at %s.", path.as_string_ptr(), sessionFile.toStdString().c_str());
538                     continue;
539                 }
540
541                 vogl_ctypes trace_gl_ctypes(m_pTraceReader->get_sof_packet().m_pointer_sizes);
542                 pSnapshot = vogl_new(vogl_gl_state_snapshot);
543                 if (!pSnapshot->deserialize(*snapshotDoc.get_root(), *pBlob_manager, &trace_gl_ctypes))
544                 {
545                     vogl_delete(pSnapshot);
546                     pSnapshot = NULL;
547                     vogl_warning_printf("Unable to deserialize the snapshot with uuid %s.", uuid.as_string_ptr());
548                     continue;
549                 }
550             }
551
552             vogleditor_gl_state_snapshot* pContainer = vogl_new(vogleditor_gl_state_snapshot, pSnapshot);
553             pContainer->set_edited(isEdited.as_bool());
554             pContainer->set_outdated(isOutdated.as_bool());
555
556             if (callIndex.is_valid())
557             {
558                 // the snapshot is associated with an api call
559                 vogleditor_apiCallTreeItem* pItem = m_pApicallTreeModel->find_call_number(callIndex.as_uint64());
560                 if (pItem != NULL)
561                 {
562                     pItem->set_snapshot(pContainer);
563                 }
564                 else
565                 {
566                     vogl_warning_printf("Unable to find API call index %" PRIu64 " to load the snapshot into.", callIndex.as_uint64());
567                     if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
568                     if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
569                 }
570             }
571             else if (frameNumber.is_valid())
572             {
573                 // the snapshot is associated with a frame.
574                 // frame snapshots have the additional requirement that the snapshot itself MUST exist since
575                 // we only save a frame snapshot if it is the inital frame and it has been edited.
576                 // If we allow NULL snapshots, that we could accidently remove the initial snapshot that was loaded with the trace file.
577                 if (pSnapshot != NULL)
578                 {
579                     vogleditor_apiCallTreeItem* pItem = m_pApicallTreeModel->find_frame_number(frameNumber.as_uint64());
580                     if (pItem != NULL)
581                     {
582                         pItem->set_snapshot(pContainer);
583                     }
584                     else
585                     {
586                         vogl_warning_printf("Unable to find frame number %" PRIu64 " to load the snapshot into.", frameNumber.as_uint64());
587                         if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
588                         if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
589                     }
590                 }
591             }
592             else
593             {
594                 vogl_warning_printf("Session file contains invalid call or frame number for snapshot with uuid %s", uuid.as_string_ptr());
595                 if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
596                 if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
597             }
598         }
599     }
600
601     return true;
602 }
603
604 /*
605  * Below is a summary of the information that needs to be saved out in a session's json file so that we can reload the session and be fully-featured.
606  * Note that not all of this information is currently supported (either by VoglEditor or the save/load functionality).
607  *
608  * sample data structure for version 1:
609 {
610    "metadata" : {
611       "session_file_format_version" : "0x1"  <- would need to be updated when organization of existing data is changed
612    },
613    "base_trace_file" : {
614       "path" : "../traces/trimmed4.bin",
615       "uuid" : [ 2761638124, 1361789091, 2623121922, 1789156619 ]
616    },
617    "session_data" : {
618       "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/",
619       "snapshots" : [
620          {
621             "uuid" : "B346B680801ED2F5144E421DEA5EFDCC",
622             "is_valid" : true,
623             "is_edited" : false,
624             "is_outdated" : false,
625             "frame_number" : 0
626          },
627          {
628             "uuid" : "BC261B884088DBEADF376A03A489F2B9",
629             "is_valid" : true,
630             "is_edited" : false,
631             "is_outdated" : false,
632             "call_index" : 881069,
633             "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/snapshot_call_881069.json"
634          },
635          {
636             "uuid" : "176DE3DEAA437B871FE122C84D5432E3",
637             "is_valid" : true,
638             "is_edited" : true,
639             "is_outdated" : false,
640             "call_index" : 881075,
641             "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/snapshot_call_881075.json"
642          },
643          {
644             "is_valid" : false,
645             "is_edited" : false,
646             "is_outdated" : true,
647             "call_index" : 881080
648          }
649       ]
650    }
651 }
652 */
653 bool VoglEditor::save_session_to_disk(QString sessionFile)
654 {
655     dynamic_string sessionPathName;
656     dynamic_string sessionFileName;
657     file_utils::split_path(sessionFile.toStdString().c_str(), sessionPathName, sessionFileName);
658
659     // modify the session file name to make a sessiondata folder
660     QString sessionDataFolder(sessionFileName.c_str());
661     int lastIndex = sessionDataFolder.lastIndexOf('.');
662     if (lastIndex != -1)
663     {
664         sessionDataFolder = sessionDataFolder.remove(lastIndex, sessionDataFolder.size() - lastIndex);
665     }
666     sessionDataFolder += "-sessiondata/";
667
668     dynamic_string sessionDataPath = sessionPathName;
669     sessionDataPath.append(sessionDataFolder.toStdString().c_str());
670     file_utils::create_directories(sessionDataPath, false);
671
672     vogl_loose_file_blob_manager file_blob_manager;
673     file_blob_manager.init(cBMFReadWrite, sessionDataPath.c_str());
674     vogl_blob_manager* pBlob_manager = static_cast<vogl_blob_manager*>(&file_blob_manager);
675
676     QCursor origCursor = this->cursor();
677     setCursor(Qt::WaitCursor);
678
679     json_document sessionDoc;
680     json_node& metadata = sessionDoc.get_root()->add_object("metadata");
681     metadata.add_key_value("session_file_format_version", to_hex_string(VOGLEDITOR_SESSION_FILE_FORMAT_VERSION));
682
683     // find relative path from session file to trace file
684     QDir relativeAppDir;
685     QString absoluteTracePath = relativeAppDir.absoluteFilePath(m_openFilename.toStdString().c_str());
686     QDir absoluteSessionFileDir(sessionPathName.c_str());
687     QString tracePathRelativeToSessionFile = absoluteSessionFileDir.relativeFilePath(absoluteTracePath);
688
689     json_node& baseTraceFile = sessionDoc.get_root()->add_object("base_trace_file");
690     baseTraceFile.add_key_value("rel_path", tracePathRelativeToSessionFile.toStdString().c_str());
691     json_node &uuid_array = baseTraceFile.add_array("uuid");
692     for (uint i = 0; i < VOGL_ARRAY_SIZE(m_pTraceReader->get_sof_packet().m_uuid); i++)
693     {
694         uuid_array.add_value(m_pTraceReader->get_sof_packet().m_uuid[i]);
695     }
696
697     json_node& sessionDataNode = sessionDoc.get_root()->add_object("session_data");
698     sessionDataNode.add_key_value("rel_path", sessionDataFolder.toStdString().c_str());
699     json_node& snapshotArray = sessionDataNode.add_array("snapshots");
700
701     vogleditor_apiCallTreeItem* pItem = m_pApicallTreeModel->find_next_snapshot(NULL);
702     vogleditor_apiCallTreeItem* pLastItem = NULL;
703     bool bSavedSuccessfully = true;
704     while (pItem != pLastItem && pItem != NULL)
705     {
706         dynamic_string filename;
707
708         json_node& snapshotNode = snapshotArray.add_object();
709         if (pItem->get_snapshot()->get_snapshot() != NULL)
710         {
711             dynamic_string strUUID;
712             snapshotNode.add_key_value("uuid", pItem->get_snapshot()->get_snapshot()->get_uuid().get_string(strUUID));
713         }
714         snapshotNode.add_key_value("is_valid", pItem->get_snapshot()->is_valid());
715         snapshotNode.add_key_value("is_edited", pItem->get_snapshot()->is_edited());
716         snapshotNode.add_key_value("is_outdated", pItem->get_snapshot()->is_outdated());
717
718         if (pItem->apiCallItem() != NULL)
719         {
720             uint64_t callIndex = pItem->apiCallItem()->globalCallIndex();
721             snapshotNode.add_key_value("call_index", callIndex);
722             if (pItem->get_snapshot()->get_snapshot() != NULL)
723             {
724                 filename = filename.format("snapshot_call_%" PRIu64 ".json", callIndex);
725                 snapshotNode.add_key_value("rel_path", filename);
726                 dynamic_string filepath = sessionDataPath;
727                 filepath.append(filename);
728                 if (!save_snapshot_to_disk(pItem->get_snapshot()->get_snapshot(), filepath, pBlob_manager))
729                 {
730                     bSavedSuccessfully = false;
731                     break;
732                 }
733             }
734         }
735         else if (pItem->frameItem() != NULL)
736         {
737             // the first frame of a trim will have a snapshot.
738             // this should only be saved out if the snapshot has been edited
739             uint64_t frameNumber = pItem->frameItem()->frameNumber();
740             snapshotNode.add_key_value("frame_number", frameNumber);
741             if (pItem->get_snapshot()->is_edited())
742             {
743                 filename = filename.format("snapshot_frame_%" PRIu64 ".json", frameNumber);
744                 snapshotNode.add_key_value("rel_path", filename);
745                 dynamic_string filepath = sessionDataPath;
746                 filepath.append(filename);
747                 if (!save_snapshot_to_disk(pItem->get_snapshot()->get_snapshot(), filepath, pBlob_manager))
748                 {
749                     bSavedSuccessfully = false;
750                     break;
751                 }
752             }
753         }
754
755         pLastItem = pItem;
756         pItem = m_pApicallTreeModel->find_next_snapshot(pLastItem);
757     }
758
759     if (bSavedSuccessfully)
760     {
761         bSavedSuccessfully = sessionDoc.serialize_to_file(sessionFile.toStdString().c_str());
762     }
763
764     setCursor(origCursor);
765
766     return bSavedSuccessfully;
767 }
768
769 bool VoglEditor::save_snapshot_to_disk(vogl_gl_state_snapshot *pSnapshot, dynamic_string filename, vogl_blob_manager *pBlob_manager)
770 {
771     if (pSnapshot == NULL)
772     {
773         return false;
774     }
775
776     json_document doc;
777
778     vogl_ctypes trace_gl_ctypes(m_pTraceReader->get_sof_packet().m_pointer_sizes);
779
780     if (!pSnapshot->serialize(*doc.get_root(), *pBlob_manager, &trace_gl_ctypes))
781     {
782         vogl_error_printf("Failed serializing state snapshot document!\n");
783         return false;
784     }
785     else if (!doc.serialize_to_file(filename.get_ptr(), true))
786     {
787         vogl_error_printf("Failed writing state snapshot to file \"%s\"!\n", filename.get_ptr());
788         return false;
789     }
790     else
791     {
792         vogl_printf("Successfully wrote JSON snapshot to file \"%s\"\n", filename.get_ptr());
793     }
794
795     return true;
796 }
797
798 //----------------------------------------------------------------------------------------------------------------------
799 // read_state_snapshot_from_trace
800 //----------------------------------------------------------------------------------------------------------------------
801 vogl_gl_state_snapshot* VoglEditor::read_state_snapshot_from_trace(vogl_trace_file_reader* pTrace_reader)
802 {
803    vogl_ctypes trace_gl_ctypes(pTrace_reader->get_sof_packet().m_pointer_sizes);
804
805    vogl_trace_packet keyframe_trace_packet(&trace_gl_ctypes);
806
807    pTrace_reader->seek_to_frame(0);
808
809    vogl_gl_state_snapshot *pSnapshot = NULL;
810    bool found_snapshot = false;
811    do
812    {
813       vogl_trace_file_reader::trace_file_reader_status_t read_status = pTrace_reader->read_next_packet();
814
815       if ((read_status != vogl_trace_file_reader::cOK) && (read_status != vogl_trace_file_reader::cEOF))
816       {
817          vogl_error_printf("%s: Failed reading from keyframe trace file!\n", VOGL_FUNCTION_NAME);
818          return NULL;
819       }
820
821       if ((read_status == vogl_trace_file_reader::cEOF) || (pTrace_reader->get_packet_type() == cTSPTEOF))
822       {
823          vogl_error_printf("%s: Failed finding state snapshot in keyframe file!\n", VOGL_FUNCTION_NAME);
824          return NULL;
825       }
826
827       if (pTrace_reader->get_packet_type() != cTSPTGLEntrypoint)
828          continue;
829
830       if (!keyframe_trace_packet.deserialize(pTrace_reader->get_packet_buf().get_ptr(), pTrace_reader->get_packet_buf().size(), false))
831       {
832          vogl_error_printf("%s: Failed parsing GL entrypoint packet in keyframe file\n", VOGL_FUNCTION_NAME);
833          return NULL;
834       }
835
836       const vogl_trace_gl_entrypoint_packet *pGL_packet = &pTrace_reader->get_packet<vogl_trace_gl_entrypoint_packet>();
837       gl_entrypoint_id_t entrypoint_id = static_cast<gl_entrypoint_id_t>(pGL_packet->m_entrypoint_id);
838
839       if (vogl_is_swap_buffers_entrypoint(entrypoint_id) || vogl_is_draw_entrypoint(entrypoint_id) || vogl_is_make_current_entrypoint(entrypoint_id))
840       {
841          vogl_error_printf("Failed finding state snapshot in keyframe file!\n");
842          return NULL;
843       }
844
845       switch (entrypoint_id)
846       {
847          case VOGL_ENTRYPOINT_glInternalTraceCommandRAD:
848          {
849             GLuint cmd = keyframe_trace_packet.get_param_value<GLuint>(0);
850             GLuint size = keyframe_trace_packet.get_param_value<GLuint>(1); VOGL_NOTE_UNUSED(size);
851
852             if (cmd == cITCRKeyValueMap)
853             {
854                key_value_map &kvm = keyframe_trace_packet.get_key_value_map();
855
856                dynamic_string cmd_type(kvm.get_string("command_type"));
857                if (cmd_type == "state_snapshot")
858                {
859                   dynamic_string id(kvm.get_string("binary_id"));
860                   if (id.is_empty())
861                   {
862                      vogl_error_printf("%s: Missing binary_id field in glInternalTraceCommandRAD key_valye_map command type: \"%s\"\n", VOGL_FUNCTION_NAME, cmd_type.get_ptr());
863                      return NULL;
864                   }
865
866                   uint8_vec snapshot_data;
867                   {
868                      timed_scope ts("get_multi_blob_manager().get");
869                      if (!pTrace_reader->get_multi_blob_manager().get(id, snapshot_data) || (snapshot_data.is_empty()))
870                      {
871                         vogl_error_printf("%s: Failed reading snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
872                         return NULL;
873                      }
874                   }
875
876                   vogl_message_printf("%s: Deserializing state snapshot \"%s\", %u bytes\n", VOGL_FUNCTION_NAME, id.get_ptr(), snapshot_data.size());
877
878                   json_document doc;
879                   {
880                      timed_scope ts("doc.binary_deserialize");
881                      if (!doc.binary_deserialize(snapshot_data) || (!doc.get_root()))
882                      {
883                         vogl_error_printf("%s: Failed deserializing JSON snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
884                         return NULL;
885                      }
886                   }
887
888                   pSnapshot = vogl_new(vogl_gl_state_snapshot);
889
890                   timed_scope ts("pSnapshot->deserialize");
891                   if (!pSnapshot->deserialize(*doc.get_root(), pTrace_reader->get_multi_blob_manager(), &trace_gl_ctypes))
892                   {
893                      vogl_delete(pSnapshot);
894                      pSnapshot = NULL;
895
896                      vogl_error_printf("%s: Failed deserializing snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
897                      return NULL;
898                   }
899
900                   found_snapshot = true;
901                }
902             }
903
904             break;
905          }
906          default: break;
907       }
908
909    } while (!found_snapshot);
910
911    return pSnapshot;
912 }
913
914
915 bool VoglEditor::open_trace_file(dynamic_string filename)
916 {
917    QCursor origCursor = this->cursor();
918    this->setCursor(Qt::WaitCursor);
919
920    vogl_loose_file_blob_manager file_blob_manager;
921    dynamic_string keyframe_trace_path(file_utils::get_pathname(filename.get_ptr()));
922    file_blob_manager.init(cBMFReadable, keyframe_trace_path.get_ptr());
923
924    dynamic_string actual_keyframe_filename;
925
926    vogl_trace_file_reader* tmpReader = vogl_open_trace_file(filename, actual_keyframe_filename, NULL);
927
928    if (tmpReader == NULL)
929    {
930       m_statusLabel->setText("Failed to open: ");
931       m_statusLabel->setText(m_statusLabel->text().append(filename.c_str()));
932       this->setCursor(origCursor);
933       return false;
934    }
935    else
936    {
937        m_statusLabel->clear();
938    }
939
940    // now that we know the new trace file can be opened,
941    // close the old one, and update the trace reader
942    close_trace_file();
943    m_pTraceReader = tmpReader;
944
945    vogl_ctypes trace_ctypes;
946    trace_ctypes.init(m_pTraceReader->get_sof_packet().m_pointer_sizes);
947
948    m_pApicallTreeModel = new vogleditor_QApiCallTreeModel(m_pTraceReader);
949    ui->treeView->setModel(m_pApicallTreeModel);
950
951    if (ui->treeView->selectionModel() != NULL)
952    {
953       //connect(ui->treeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(on_treeView_selectionChanged(const QItemSelection&, const QItemSelection&)));
954       connect(ui->treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(on_treeView_currentChanged(const QModelIndex &, const QModelIndex &)));
955    }
956
957    if (m_pApicallTreeModel->hasChildren())
958    {
959       ui->treeView->setExpanded(m_pApicallTreeModel->index(0,0), true);
960       ui->treeView->setCurrentIndex(m_pApicallTreeModel->index(0,0));
961    }
962
963    int flagsColumnWidth = 30;
964    ui->treeView->header()->setMinimumSectionSize(flagsColumnWidth);
965    ui->treeView->header()->moveSection(VOGL_ACTC_FLAGS, 0);
966    ui->treeView->setColumnWidth(VOGL_ACTC_FLAGS, flagsColumnWidth);
967
968    int width = ui->treeView->width() - flagsColumnWidth - 30; // subtract a little extra for the scrollbar width
969    ui->treeView->setColumnWidth(VOGL_ACTC_APICALL, width * 0.7);
970    ui->treeView->setColumnWidth(VOGL_ACTC_INDEX, width * 0.15);
971    ui->treeView->setColumnWidth(VOGL_ACTC_DURATION, width * 0.15);
972
973    ui->searchTextBox->setEnabled(true);
974    ui->searchPrevButton->setEnabled(true);
975    ui->searchNextButton->setEnabled(true);
976
977    ui->action_Close->setEnabled(true);
978    ui->actionSave_Session->setEnabled(true);
979    ui->actionExport_API_Calls->setEnabled(true);
980
981    ui->prevSnapshotButton->setEnabled(true);
982    ui->nextSnapshotButton->setEnabled(true);
983    ui->prevDrawcallButton->setEnabled(true);
984    ui->nextDrawcallButton->setEnabled(true);
985
986    m_backtraceToJsonMap.clear();
987    m_backtraceDoc.clear();
988
989     // Extract backtrace map and machine info from trace archive
990     if (m_pTraceReader->get_archive_blob_manager().is_initialized())
991     {
992         // backtrace
993         uint8_vec backtrace_data;
994         bool bBacktraceVisible = false;
995         if (m_pTraceReader->get_archive_blob_manager().does_exist(VOGL_TRACE_ARCHIVE_BACKTRACE_MAP_ADDRS_FILENAME))
996         {
997             //$ TODO mikesart: read MAP_SYMS data here when symbols have been resolved.
998             if (m_pTraceReader->get_archive_blob_manager().get(VOGL_TRACE_ARCHIVE_BACKTRACE_MAP_ADDRS_FILENAME, backtrace_data))
999             {
1000                 json_node* pRoot = m_backtraceDoc.get_root();
1001                 if (m_backtraceDoc.deserialize((const char*)backtrace_data.get_ptr(), backtrace_data.size()))
1002                 {
1003                     bBacktraceVisible = pRoot->size() > 0;
1004                     for (uint i = 0; i < pRoot->size(); i++)
1005                     {
1006                         json_node* pChild = pRoot->get_child(i);
1007                         uint32 index = 0;
1008                         VOGL_ASSERT("Backtrace node does not have an 'index' child" && pChild != NULL && pChild->get_value_as_uint32("index", index));
1009                         if (pChild != NULL && pChild->get_value_as_uint32("index", index))
1010                         {
1011                             m_backtraceToJsonMap.insert(index, pChild);
1012                         }
1013                     }
1014                 }
1015             }
1016         }
1017
1018         QList<int> backtraceSplitterSizes = ui->splitter_3->sizes();
1019         int backtraceSplitterTotalSize = backtraceSplitterSizes[0] + backtraceSplitterSizes[1];
1020         QList<int> newBacktraceSplitterSizes;
1021         if (!bBacktraceVisible)
1022         {
1023             newBacktraceSplitterSizes.append(backtraceSplitterTotalSize);
1024             newBacktraceSplitterSizes.append(0);
1025             ui->splitter_3->setSizes(newBacktraceSplitterSizes);
1026         }
1027         else
1028         {
1029             newBacktraceSplitterSizes << (backtraceSplitterTotalSize * 0.75)
1030                                       << (backtraceSplitterTotalSize * 0.25);
1031             ui->splitter_3->setSizes(newBacktraceSplitterSizes);
1032         }
1033
1034         // machine info
1035         displayMachineInfo();
1036    }
1037
1038    m_openFilename = filename.c_str();
1039
1040    setWindowTitle(m_openFilename + " - " + g_PROJECT_NAME);
1041
1042    ui->tabWidget->setCurrentWidget(ui->framebufferTab);
1043
1044    // update toolbar
1045    m_pPlayButton->setEnabled(true);
1046    m_pPauseButton->setEnabled(false);
1047    m_pTrimButton->setEnabled(false);
1048    m_pStopButton->setEnabled(false);
1049
1050    // timeline
1051    m_pTimelineModel = new vogleditor_apiCallTimelineModel(m_pApicallTreeModel->root());
1052    m_timeline->setModel(m_pTimelineModel);
1053    m_timeline->repaint();
1054
1055    this->setCursor(origCursor);
1056    return true;
1057 }
1058
1059 void VoglEditor::displayMachineInfoHelper(QString prefix, const QString& sectionKeyStr, const vogl::json_value& value, QString& rMachineInfoStr)
1060 {
1061     if (value.is_array())
1062     {
1063         const json_node* pNode = value.get_node_ptr();
1064         for (uint element = 0; element < pNode->size(); element++)
1065         {
1066             dynamic_string elementStr = pNode->get_value(element).as_string();
1067
1068             elementStr = elementStr.replace("\n", "\n\t");
1069
1070             rMachineInfoStr += "\t";
1071             rMachineInfoStr += elementStr.get_ptr();
1072             rMachineInfoStr += "\n";
1073         }
1074
1075         rMachineInfoStr += "\n";
1076     }
1077     else if (value.is_node())
1078     {
1079         // Check if this is the modoule list.
1080         bool is_module_list = (sectionKeyStr == "module_list");
1081         const json_node* pNode = value.get_node_ptr();
1082
1083         for (uint i = 0; i < pNode->size(); i++)
1084         {
1085             dynamic_string key = pNode->get_key(i);
1086             const json_value &value2 = pNode->get_value(i);
1087
1088             rMachineInfoStr += prefix;
1089             // If it's the module list, then the key is the filename and we want to display that last.
1090             if (!is_module_list)
1091                 rMachineInfoStr += key.c_str();
1092
1093             if (value2.is_array())
1094             {
1095                 const json_node* pNode2 = value2.get_node_ptr();
1096
1097                 // If this it module_list, then we get these items: base address, address size, uuid
1098                 // Check in btrace_get_machine_info() to see what's written there.
1099                 for (uint element = 0; element < pNode2->size(); element++)
1100                 {
1101                     const json_value &json_val = pNode2->get_value(element);
1102
1103                     if (json_val.is_string())
1104                     {
1105                         dynamic_string str = pNode2->get_value(element).as_string();
1106                         rMachineInfoStr += str.c_str();
1107                     }
1108                     else
1109                     {
1110                         dynamic_string buf;
1111                         buf.format("%" PRIx64, json_val.as_uint64());
1112                         rMachineInfoStr += buf.c_str();
1113                     }
1114
1115                     rMachineInfoStr += "\t";
1116                 }
1117             }
1118             else
1119             {
1120                 rMachineInfoStr += ": ";
1121                 rMachineInfoStr += value2.as_string_ptr();
1122             }
1123
1124             // Display the filename if this is the module_list.
1125             if (is_module_list)
1126                 rMachineInfoStr += key.c_str();
1127             rMachineInfoStr += "\n";
1128         }
1129
1130         rMachineInfoStr += "\n";
1131     }
1132     else if (value.is_string())
1133     {
1134         rMachineInfoStr += value.as_string_ptr();
1135     }
1136     else
1137     {
1138         rMachineInfoStr += value.as_string_ptr();
1139     }
1140 }
1141
1142 void VoglEditor::displayMachineInfo()
1143 {
1144     VOGL_ASSERT(m_pTraceReader != NULL);
1145
1146     bool bMachineInfoVisible = false;
1147     if (m_pTraceReader->get_archive_blob_manager().does_exist(VOGL_TRACE_ARCHIVE_MACHINE_INFO_FILENAME))
1148     {
1149         uint8_vec machine_info_data;
1150         if (m_pTraceReader->get_archive_blob_manager().get(VOGL_TRACE_ARCHIVE_MACHINE_INFO_FILENAME, machine_info_data))
1151         {
1152             bMachineInfoVisible = true;
1153             json_document doc;
1154             json_node *pRoot = doc.get_root();
1155             if (doc.deserialize((const char*)machine_info_data.get_ptr(), machine_info_data.size()))
1156             {
1157                 QString text;
1158                 for (uint i = 0; i < pRoot->size(); i++)
1159                 {
1160                     dynamic_string sectionKeyStr = pRoot->get_key(i);
1161                     text += pRoot->get_key(i).c_str();
1162                     text += "\n";
1163
1164                     QString keyStr = sectionKeyStr.c_str();
1165                     displayMachineInfoHelper("\t", keyStr, pRoot->get_value(i), text);
1166                 }
1167
1168                 ui->machineInfoText->setText(text);
1169             }
1170         }
1171     }
1172
1173     if (bMachineInfoVisible)
1174     {
1175         if (ui->tabWidget->indexOf(ui->machineInfoTab) == -1)
1176         {
1177             // unhide the tab
1178             ui->tabWidget->insertTab(0, ui->machineInfoTab, "Machine Info");
1179         }
1180         else
1181         {
1182             VOGLEDITOR_ENABLE_TAB(ui->machineInfoTab);
1183         }
1184     }
1185     else
1186     {
1187         ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->machineInfoTab));
1188     }
1189 }
1190
1191 void VoglEditor::reset_tracefile_ui()
1192 {
1193     ui->action_Close->setEnabled(false);
1194     ui->actionExport_API_Calls->setEnabled(false);
1195     ui->actionSave_Session->setEnabled(false);
1196
1197     ui->prevSnapshotButton->setEnabled(false);
1198     ui->nextSnapshotButton->setEnabled(false);
1199     ui->prevDrawcallButton->setEnabled(false);
1200     ui->nextDrawcallButton->setEnabled(false);
1201     ui->searchTextBox->clear();
1202     ui->searchTextBox->setEnabled(false);
1203     ui->searchPrevButton->setEnabled(false);
1204     ui->searchNextButton->setEnabled(false);
1205
1206     m_statusLabel->clear();
1207     m_pPlayButton->setEnabled(false);
1208     m_pPauseButton->setEnabled(false);
1209     m_pTrimButton->setEnabled(false);
1210     m_pStopButton->setEnabled(false);
1211
1212     VOGLEDITOR_DISABLE_TAB(ui->machineInfoTab);
1213
1214     reset_snapshot_ui();
1215 }
1216
1217 void VoglEditor::reset_snapshot_ui()
1218 {
1219     m_currentSnapshot = NULL;
1220
1221     m_framebufferExplorer->clear();
1222     m_textureExplorer->clear();
1223     m_renderbufferExplorer->clear();
1224     m_programExplorer->clear();
1225     m_shaderExplorer->clear();
1226
1227     ui->stateTreeView->setModel(NULL);
1228
1229     QWidget* pCurrentTab = ui->tabWidget->currentWidget();
1230
1231     VOGLEDITOR_DISABLE_TAB(ui->stateTab);
1232     VOGLEDITOR_DISABLE_TAB(ui->framebufferTab);
1233     VOGLEDITOR_DISABLE_TAB(ui->programTab);
1234     VOGLEDITOR_DISABLE_TAB(ui->shaderTab);
1235     VOGLEDITOR_DISABLE_TAB(ui->textureTab);
1236     VOGLEDITOR_DISABLE_TAB(ui->renderbufferTab);
1237
1238     ui->tabWidget->setCurrentWidget(pCurrentTab);
1239 }
1240
1241 /// This helper will most often return a pointer equal to the pCurSnapshot that is passed in, or NULL if the node does not have a snapshot
1242 /// and also has no children. The pMostRecentSnapshot parameter will be updated to point to the desired snapshot.
1243 /// This function does not follow a traditional DFS search because we need to find the desired snapshot then return the one before it.
1244 /// An alternative approach would be to keep a stack of the found snapshots, or even to build up that stack / list as the user
1245 /// generates new snapshots.
1246 vogleditor_gl_state_snapshot* VoglEditor::findMostRecentSnapshot_helper(vogleditor_apiCallTreeItem* pItem, vogleditor_gl_state_snapshot*& pMostRecentSnapshot, const vogleditor_gl_state_snapshot* pCurSnapshot)
1247 {
1248     // check if this item has a snapshot shot
1249     if (pItem->has_snapshot())
1250     {
1251         vogleditor_gl_state_snapshot* pTmp = pItem->get_snapshot();
1252         if (pTmp == pCurSnapshot)
1253         {
1254             // if we've reached the item with the current snapshot, we want to return the previous snapshot.
1255             return pTmp;
1256         }
1257         else
1258         {
1259             // update most recent snapshot
1260             pMostRecentSnapshot = pTmp;
1261         }
1262     }
1263
1264     for (int i = 0; i < pItem->childCount(); i++)
1265     {
1266         vogleditor_gl_state_snapshot* pTmp = findMostRecentSnapshot_helper(pItem->child(i), pMostRecentSnapshot, pCurSnapshot);
1267         if (pTmp != NULL)
1268         {
1269             if (pTmp == pCurSnapshot)
1270             {
1271                 // if we've reached the item with the current snapshot, we want to return the previous snapshot.
1272                 return pTmp;
1273             }
1274             else
1275             {
1276                 // update most recent snapshot
1277                 pMostRecentSnapshot = pTmp;
1278             }
1279         }
1280     }
1281
1282     return NULL;
1283 }
1284
1285 /// This function exists just to simplify the interaction with the helper, so that there no confusion between
1286 /// whether the returned value, or passed in reference parameter should be used as the most recent snapshot.
1287 /// It will either return NULL if there is no recent snapshot (which should only happen for the very first snapshot
1288 /// in a trace), or a pointer to a valid snapshot.
1289 vogleditor_gl_state_snapshot* VoglEditor::findMostRecentSnapshot(vogleditor_apiCallTreeItem* pItem, const vogleditor_gl_state_snapshot* pCurSnapshot)
1290 {
1291     vogleditor_gl_state_snapshot* pMostRecentSnapshot = NULL;
1292     findMostRecentSnapshot_helper(pItem, pMostRecentSnapshot, pCurSnapshot);
1293     return pMostRecentSnapshot;
1294 }
1295
1296 void VoglEditor::update_ui_for_snapshot(vogleditor_gl_state_snapshot* pStateSnapshot)
1297 {
1298    if (pStateSnapshot == NULL)
1299    {
1300       reset_snapshot_ui();
1301       return;
1302    }
1303
1304    if (pStateSnapshot->is_valid() == false)
1305    {
1306        reset_snapshot_ui();
1307        return;
1308    }
1309
1310    if (m_currentSnapshot == pStateSnapshot)
1311    {
1312        // no need to update if it is the same snapshot
1313        return;
1314    }
1315
1316    m_currentSnapshot = pStateSnapshot;
1317
1318    if (ui->stateTreeView->model() != NULL)
1319    {
1320       if (static_cast<vogleditor_QStateTreeModel*>(ui->stateTreeView->model())->get_snapshot() == m_currentSnapshot)
1321       {
1322          // displaying the same snapshot, return
1323          return;
1324       }
1325    }
1326
1327    QCursor origCursor = this->cursor();
1328    this->setCursor(Qt::WaitCursor);
1329
1330    // state viewer
1331    vogleditor_QStateTreeModel* pStateModel = new vogleditor_QStateTreeModel(NULL);
1332
1333    vogleditor_QApiCallTreeModel* pTreeModel = static_cast<vogleditor_QApiCallTreeModel*>(ui->treeView->model());
1334    vogleditor_gl_state_snapshot* pBaseSnapshot = findMostRecentSnapshot(pTreeModel->root(), m_currentSnapshot);
1335    pStateModel->set_diff_base_snapshot(pBaseSnapshot);
1336
1337    pStateModel->set_snapshot(pStateSnapshot);
1338
1339    ui->stateTreeView->setModel(pStateModel);
1340    ui->stateTreeView->expandToDepth(1);
1341    ui->stateTreeView->setColumnWidth(0, ui->stateTreeView->width() * 0.5);
1342
1343    VOGLEDITOR_ENABLE_TAB(ui->stateTab);
1344
1345    if (pStateSnapshot->get_contexts().size() > 0)
1346    {
1347        vogl_trace_ptr_value curContextHandle = pStateSnapshot->get_cur_trace_context();
1348        if (curContextHandle != 0)
1349        {
1350            vogl_context_snapshot* pContext = pStateSnapshot->get_context(curContextHandle);
1351
1352            // textures
1353            vogl_gl_object_state_ptr_vec textureObjects;
1354            pContext->get_all_objects_of_category(cGLSTTexture, textureObjects);
1355            m_textureExplorer->set_texture_objects(textureObjects);
1356
1357            GLuint curActiveTextureUnit = pContext->get_general_state().get_value<GLuint>(GL_ACTIVE_TEXTURE);
1358            if (curActiveTextureUnit >= GL_TEXTURE0 && curActiveTextureUnit < (GL_TEXTURE0 + pContext->get_context_info().get_max_texture_image_units()))
1359            {
1360                GLuint cur2DBinding = pContext->get_general_state().get_value<GLuint>(GL_TEXTURE_2D_BINDING_EXT, curActiveTextureUnit - GL_TEXTURE0);
1361                displayTexture(cur2DBinding, false);
1362            }
1363
1364            // renderbuffers
1365            vogl_gl_object_state_ptr_vec renderbufferObjects;
1366            pContext->get_all_objects_of_category(cGLSTRenderbuffer, renderbufferObjects);
1367            m_renderbufferExplorer->set_texture_objects(renderbufferObjects);
1368            if (renderbufferObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->renderbufferTab); }
1369
1370            // framebuffer
1371            vogl_gl_object_state_ptr_vec framebufferObjects;
1372            pContext->get_all_objects_of_category(cGLSTFramebuffer, framebufferObjects);
1373            m_framebufferExplorer->set_framebuffer_objects(framebufferObjects, *pContext, pStateSnapshot->get_default_framebuffer());
1374            GLuint64 curDrawFramebuffer = pContext->get_general_state().get_value<GLuint64>(GL_DRAW_FRAMEBUFFER_BINDING);
1375            displayFramebuffer(curDrawFramebuffer, false);
1376
1377            // programs
1378            vogl_gl_object_state_ptr_vec programObjects;
1379            pContext->get_all_objects_of_category(cGLSTProgram, programObjects);
1380            m_programExplorer->set_program_objects(programObjects);
1381            GLuint64 curProgram = pContext->get_general_state().get_value<GLuint64>(GL_CURRENT_PROGRAM);
1382            m_programExplorer->set_active_program(curProgram);
1383            if (programObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->programTab); }
1384
1385            // shaders
1386            vogl_gl_object_state_ptr_vec shaderObjects;
1387            pContext->get_all_objects_of_category(cGLSTShader, shaderObjects);
1388            m_shaderExplorer->set_shader_objects(shaderObjects);
1389            if (curProgram != 0)
1390            {
1391                for (vogl_gl_object_state_ptr_vec::iterator iter = programObjects.begin(); iter != programObjects.end(); iter++)
1392                {
1393                    if ((*iter)->get_snapshot_handle() == curProgram)
1394                    {
1395                        vogl_program_state* pProgramState = static_cast<vogl_program_state*>(*iter);
1396                        if (pProgramState->get_attached_shaders().size() > 0)
1397                        {
1398                            uint curShader = pProgramState->get_attached_shaders()[0];
1399                            m_shaderExplorer->set_active_shader(curShader);
1400                        }
1401                        break;
1402                    }
1403                }
1404            }
1405            if (shaderObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->shaderTab); }
1406        }
1407    }
1408
1409    this->setCursor(origCursor);
1410 }
1411
1412 void VoglEditor::on_stateTreeView_clicked(const QModelIndex &index)
1413 {
1414    vogleditor_stateTreeItem* pStateItem = static_cast<vogleditor_stateTreeItem*>(index.internalPointer());
1415    if (pStateItem == NULL)
1416    {
1417       return;
1418    }
1419
1420    switch(pStateItem->getStateType())
1421    {
1422    case vogleditor_stateTreeItem::cTEXTURE:
1423    {
1424       vogleditor_stateTreeTextureItem* pTextureItem = static_cast<vogleditor_stateTreeTextureItem*>(pStateItem);
1425       if (pTextureItem == NULL)
1426       {
1427          break;
1428       }
1429
1430       displayTexture(pTextureItem->get_texture_state()->get_snapshot_handle(), true);
1431
1432       break;
1433    }
1434    case vogleditor_stateTreeItem::cPROGRAM:
1435    {
1436       vogleditor_stateTreeProgramItem* pProgramItem = static_cast<vogleditor_stateTreeProgramItem*>(pStateItem);
1437       if (pProgramItem == NULL)
1438       {
1439          break;
1440       }
1441
1442       displayProgram(pProgramItem->get_current_state()->get_snapshot_handle(), true);
1443
1444       break;
1445    }
1446    case vogleditor_stateTreeItem::cSHADER:
1447    {
1448       vogleditor_stateTreeShaderItem* pShaderItem = static_cast<vogleditor_stateTreeShaderItem*>(pStateItem);
1449       if (pShaderItem == NULL)
1450       {
1451          break;
1452       }
1453
1454       displayShader(pShaderItem->get_current_state()->get_snapshot_handle(), true);
1455
1456       break;
1457    }
1458    case vogleditor_stateTreeItem::cFRAMEBUFFER:
1459    {
1460       vogleditor_stateTreeFramebufferItem* pFramebufferItem = static_cast<vogleditor_stateTreeFramebufferItem*>(pStateItem);
1461       if (pFramebufferItem == NULL)
1462       {
1463          break;
1464       }
1465
1466       displayFramebuffer(pFramebufferItem->get_framebuffer_state()->get_snapshot_handle(), true);
1467
1468       break;
1469    }
1470    case vogleditor_stateTreeItem::cDEFAULT:
1471    {
1472       return;
1473    }
1474    }
1475 }
1476
1477 bool VoglEditor::displayShader(GLuint64 shaderHandle, bool bBringTabToFront)
1478 {
1479     bool bDisplayed = false;
1480     if (m_shaderExplorer->set_active_shader(shaderHandle))
1481     {
1482         if (bBringTabToFront)
1483         {
1484             ui->tabWidget->setCurrentWidget(ui->shaderTab);
1485         }
1486     }
1487
1488     return bDisplayed;
1489 }
1490
1491 void VoglEditor::displayProgram(GLuint64 programHandle, bool bBringTabToFront)
1492 {
1493     if (m_programExplorer->set_active_program(programHandle))
1494     {
1495         if (bBringTabToFront)
1496         {
1497             ui->tabWidget->setCurrentWidget(ui->programTab);
1498         }
1499     }
1500 }
1501
1502 void VoglEditor::displayFramebuffer(GLuint64 framebufferHandle, bool bBringTabToFront)
1503 {
1504     bool bDisplayedFBO = m_framebufferExplorer->set_active_framebuffer(framebufferHandle);
1505
1506     if (bDisplayedFBO)
1507     {
1508         VOGLEDITOR_ENABLE_TAB(ui->framebufferTab);
1509         if (bBringTabToFront)
1510         {
1511             ui->tabWidget->setCurrentWidget(ui->framebufferTab);
1512         }
1513     }
1514 }
1515
1516 bool VoglEditor::displayTexture(GLuint64 textureHandle, bool bBringTabToFront)
1517 {
1518     bool bDisplayedTexture = m_textureExplorer->set_active_texture(textureHandle);
1519
1520     if (bDisplayedTexture)
1521     {
1522         VOGLEDITOR_ENABLE_TAB(ui->textureTab);
1523         if (bBringTabToFront)
1524         {
1525             ui->tabWidget->setCurrentWidget(ui->textureTab);
1526         }
1527     }
1528
1529     return bDisplayedTexture;
1530 }
1531
1532 void VoglEditor::on_treeView_currentChanged(const QModelIndex & current, const QModelIndex & previous)
1533 {
1534     VOGL_NOTE_UNUSED(previous);
1535     onApiCallSelected(current, false);
1536 }
1537
1538 void VoglEditor::on_treeView_clicked(const QModelIndex &index)
1539 {
1540     onApiCallSelected(index, true);
1541 }
1542
1543 void VoglEditor::onApiCallSelected(const QModelIndex &index, bool bAllowStateSnapshot)
1544 {
1545     vogleditor_apiCallTreeItem* pCallTreeItem = static_cast<vogleditor_apiCallTreeItem*>(index.internalPointer());
1546     if (pCallTreeItem == NULL)
1547     {
1548        return;
1549     }
1550
1551     vogleditor_frameItem* pFrameItem = pCallTreeItem->frameItem();
1552     vogleditor_apiCallItem* pApiCallItem = pCallTreeItem->apiCallItem();
1553
1554     if (bAllowStateSnapshot && pCallTreeItem == m_pCurrentCallTreeItem)
1555     {
1556         // we can only get snapshots for specific API calls
1557         if (pApiCallItem != NULL && pApiCallItem->needs_snapshot())
1558         {
1559            // get the snapshot after the current api call
1560            vogleditor_gl_state_snapshot* pNewSnapshot = NULL;
1561            QCursor origCursor = cursor();
1562            setCursor(Qt::WaitCursor);
1563            m_traceReplayer.replay(m_pTraceReader, m_pApicallTreeModel->root(), &pNewSnapshot, pApiCallItem->globalCallIndex(), false);
1564            setCursor(origCursor);
1565            pCallTreeItem->set_snapshot(pNewSnapshot);
1566         }
1567     }
1568
1569     update_ui_for_snapshot(pCallTreeItem->get_snapshot());
1570
1571     if (pApiCallItem != NULL && m_pCurrentCallTreeItem != pCallTreeItem)
1572     {
1573         if (m_backtraceToJsonMap.size() > 0)
1574         {
1575             QString tmp;
1576             json_node* pBacktraceNode = m_backtraceToJsonMap[(uint)pApiCallItem->backtraceHashIndex()];
1577             if (pBacktraceNode != NULL)
1578             {
1579                 json_node* pAddrs = pBacktraceNode->find_child_array("addrs");
1580                 json_node* pSyms = pBacktraceNode->find_child_array("syms");
1581
1582                 for (uint i = 0; i < pAddrs->size(); i++)
1583                 {
1584                     tmp += pAddrs->get_value(i).as_string_ptr();
1585                     if (pSyms)
1586                     {
1587                         tmp += "\t";
1588                         tmp += pSyms->get_value(i).as_string_ptr();
1589                     }
1590                     tmp += "\n";
1591                 }
1592             }
1593             ui->backtraceText->setText(tmp);
1594         }
1595     }
1596
1597     if (pApiCallItem != NULL)
1598     {
1599         m_timeline->setCurrentApiCall(pApiCallItem->globalCallIndex());
1600     }
1601
1602     if (pFrameItem != NULL)
1603     {
1604        m_timeline->setCurrentFrame(pFrameItem->frameNumber());
1605     }
1606
1607     m_timeline->repaint();
1608
1609     m_pCurrentCallTreeItem = pCallTreeItem;
1610 }
1611
1612 void VoglEditor::selectApicallModelIndex(QModelIndex index, bool scrollTo, bool select)
1613 {
1614     // make sure the index is visible
1615     QModelIndex parentIndex = index.parent();
1616     while (parentIndex.isValid())
1617     {
1618         if (ui->treeView->isExpanded(parentIndex) == false)
1619         {
1620             ui->treeView->expand(parentIndex);
1621         }
1622         parentIndex = parentIndex.parent();
1623     }
1624
1625     // scroll to the index
1626     if (scrollTo)
1627     {
1628         ui->treeView->scrollTo(index);
1629     }
1630
1631     // select the index
1632     if (select)
1633     {
1634         ui->treeView->setCurrentIndex(index);
1635     }
1636
1637     if (m_searchApicallResults.size() > 0 && !ui->searchTextBox->text().isEmpty())
1638     {
1639         QItemSelectionModel* pSelection = ui->treeView->selectionModel();
1640         for (int i = 0; i < m_searchApicallResults.size(); i++)
1641         {
1642             pSelection->select(m_searchApicallResults[i], QItemSelectionModel::Select | QItemSelectionModel::Rows);
1643         }
1644         ui->treeView->setSelectionModel(pSelection);
1645     }
1646 }
1647
1648 void VoglEditor::on_searchTextBox_textChanged(const QString &searchText)
1649 {
1650     QModelIndex curSearchIndex = ui->treeView->currentIndex();
1651     if (curSearchIndex.isValid() == false)
1652     {
1653         return;
1654     }
1655
1656     // store original background color of the search text box so that it can be turned to red and later restored.
1657     static const QColor sOriginalTextBoxBackground = ui->searchTextBox->palette().base().color();
1658
1659     // clear previous items
1660     QItemSelectionModel* pSelection = ui->treeView->selectionModel();
1661     if (pSelection != NULL)
1662     {
1663         for (int i = 0; i < m_searchApicallResults.size(); i++)
1664         {
1665             pSelection->select(m_searchApicallResults[i], QItemSelectionModel::Clear | QItemSelectionModel::Rows);
1666         }
1667         ui->treeView->setSelectionModel(pSelection);
1668     }
1669
1670     // find new matches
1671     m_searchApicallResults.clear();
1672     if (m_pApicallTreeModel != NULL)
1673     {
1674         m_searchApicallResults = m_pApicallTreeModel->find_search_matches(searchText);
1675     }
1676
1677     // if there are matches, restore the textbox background to its original color
1678     if (m_searchApicallResults.size() > 0)
1679     {
1680         QPalette palette(ui->searchTextBox->palette());
1681         palette.setColor(QPalette::Base, sOriginalTextBoxBackground);
1682         ui->searchTextBox->setPalette(palette);
1683     }
1684
1685     // select new items
1686     if (!searchText.isEmpty())
1687     {
1688         if (m_searchApicallResults.size() > 0)
1689         {
1690             // scroll to the first result, but don't select it
1691             selectApicallModelIndex(m_searchApicallResults[0], true, false);
1692         }
1693         else
1694         {
1695             // no items were found, so set the textbox background to red
1696             QPalette palette(ui->searchTextBox->palette());
1697             palette.setColor(QPalette::Base, Qt::red);
1698             ui->searchTextBox->setPalette(palette);
1699         }
1700     }
1701 }
1702
1703 void VoglEditor::on_searchNextButton_clicked()
1704 {
1705     if (m_pApicallTreeModel != NULL)
1706     {
1707         QModelIndex index = m_pApicallTreeModel->find_next_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
1708         selectApicallModelIndex(index, true, true);
1709     }
1710 }
1711
1712 void VoglEditor::on_searchPrevButton_clicked()
1713 {
1714     if (m_pApicallTreeModel != NULL)
1715     {
1716         QModelIndex index = m_pApicallTreeModel->find_prev_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
1717         selectApicallModelIndex(index, true, true);
1718     }
1719 }
1720
1721 void VoglEditor::on_prevSnapshotButton_clicked()
1722 {
1723     if (m_pApicallTreeModel != NULL)
1724     {
1725         vogleditor_apiCallTreeItem* pPrevItemWithSnapshot = m_pApicallTreeModel->find_prev_snapshot(m_pCurrentCallTreeItem);
1726         selectApicallModelIndex(m_pApicallTreeModel->indexOf(pPrevItemWithSnapshot), true, true);
1727         ui->treeView->setFocus();
1728     }
1729 }
1730
1731 void VoglEditor::on_nextSnapshotButton_clicked()
1732 {
1733     if (m_pApicallTreeModel != NULL)
1734     {
1735         vogleditor_apiCallTreeItem* pNextItemWithSnapshot = m_pApicallTreeModel->find_next_snapshot(m_pCurrentCallTreeItem);
1736         selectApicallModelIndex(m_pApicallTreeModel->indexOf(pNextItemWithSnapshot), true, true);
1737         ui->treeView->setFocus();
1738     }
1739 }
1740
1741 void VoglEditor::on_prevDrawcallButton_clicked()
1742 {
1743     if (m_pApicallTreeModel != NULL)
1744     {
1745         vogleditor_apiCallTreeItem* pPrevItem = m_pApicallTreeModel->find_prev_drawcall(m_pCurrentCallTreeItem);
1746         selectApicallModelIndex(m_pApicallTreeModel->indexOf(pPrevItem), true, true);
1747         ui->treeView->setFocus();
1748     }
1749 }
1750
1751 void VoglEditor::on_nextDrawcallButton_clicked()
1752 {
1753     if (m_pApicallTreeModel != NULL)
1754     {
1755         vogleditor_apiCallTreeItem* pNextItem = m_pApicallTreeModel->find_next_drawcall(m_pCurrentCallTreeItem);
1756         selectApicallModelIndex(m_pApicallTreeModel->indexOf(pNextItem), true, true);
1757         ui->treeView->setFocus();
1758     }
1759 }
1760
1761
1762 void VoglEditor::on_program_edited(vogl_program_state* pNewProgramState)
1763 {
1764     VOGL_NOTE_UNUSED(pNewProgramState);
1765
1766     m_currentSnapshot->set_edited(true);
1767
1768     // update all the snapshot flags
1769     bool bFoundEditedSnapshot = false;
1770     recursive_update_snapshot_flags(m_pApicallTreeModel->root(), bFoundEditedSnapshot);
1771
1772     // give the tree view focus so that it redraws. This is something of a hack, we don't really want to be changing around which control has focus,
1773     // but right now I don't see it being a major issue. It may be an issue later on depending on how we implement more state editing (ie, if arrow
1774     // keys are used to cycle through options in a drop-down, and the tree view gets focus, the arrow keys would then start changing the selected
1775     // API call instead of cycling through state options).
1776     ui->treeView->setFocus();
1777 }
1778
1779 // if an edited snapshot has already been found, mark the node (and all children) as dirty.
1780 void VoglEditor::recursive_update_snapshot_flags(vogleditor_apiCallTreeItem* pItem, bool& bFoundEditedSnapshot)
1781 {
1782     // check if this item has a snapshot shot
1783     if (pItem->has_snapshot())
1784     {
1785         if (!bFoundEditedSnapshot)
1786         {
1787             if (pItem->get_snapshot()->is_edited())
1788             {
1789                 bFoundEditedSnapshot = true;
1790             }
1791             else
1792             {
1793                 pItem->get_snapshot()->set_outdated(false);
1794             }
1795         }
1796         else
1797         {
1798             pItem->get_snapshot()->set_outdated(true);
1799         }
1800     }
1801
1802     for (int i = 0; i < pItem->childCount(); i++)
1803     {
1804         recursive_update_snapshot_flags(pItem->child(i), bFoundEditedSnapshot);
1805     }
1806 }
1807
1808 #undef VOGLEDITOR_DISABLE_TAB
1809 #undef VOGLEDITOR_ENABLE_TAB
1810
1811 void VoglEditor::on_actionSave_Session_triggered()
1812 {
1813     QString baseName = m_openFilename;
1814
1815     int lastIndex = baseName.lastIndexOf('.');
1816     if (lastIndex != -1)
1817     {
1818         baseName = baseName.remove(lastIndex, baseName.size() - lastIndex);
1819     }
1820
1821     QString suggestedName = baseName + "-vogleditor.json";
1822
1823     QString sessionFilename = QFileDialog::getSaveFileName(this, tr("Save Debug Session"), suggestedName, tr("JSON (*.json)"));
1824
1825     if (!save_session_to_disk(sessionFilename))
1826     {
1827         m_statusLabel->setText("ERROR: Failed to save session");
1828     }
1829 }
1830
1831 void VoglEditor::on_actionOpen_Session_triggered()
1832 {
1833     QString sessionFilename = QFileDialog::getOpenFileName(this, tr("Load Debug Session"), QString(), tr("JSON (*.json)"));
1834
1835     QCursor origCursor = this->cursor();
1836     setCursor(Qt::WaitCursor);
1837
1838     if (!load_session_from_disk(sessionFilename))
1839     {
1840         m_statusLabel->setText("ERROR: Failed to load session");
1841     }
1842
1843     setCursor(origCursor);
1844 }