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