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