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