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