]> git.cworth.org Git - vogl/blob - src/vogleditor/vogleditor.cpp
e67967e63322f1c4a8f1be0d5c6c737b1e639281
[vogl] / src / vogleditor / vogleditor.cpp
1 /**************************************************************************
2  *
3  * Copyright 2013-2014 RAD Game Tools and Valve Software
4  * All Rights Reserved.
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to deal
8  * in the Software without restriction, including without limitation the rights
9  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10  * copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22  * THE SOFTWARE.
23  *
24  **************************************************************************/
25
26 #include <QFileDialog>
27 #include <QHBoxLayout>
28 #include <QItemSelection>
29 #include <QPalette>
30 #include <QSortFilterProxyModel>
31 #include <QSpacerItem>
32 #include <QToolButton>
33 #include <QMessageBox>
34
35 #include "ui_vogleditor.h"
36 #include "vogleditor.h"
37
38 #include "vogleditor_qapicalltreemodel.h"
39 #include "vogleditor_apicalltimelinemodel.h"
40
41 #include "vogl_platform.h"
42 #include "vogl_assert.h"
43 #include "vogl_file_utils.h"
44 #include "vogl_find_files.h"
45
46 #include "vogl_texture_format.h"
47 #include "vogl_trace_file_reader.h"
48 #include "vogleditor_qstatetreemodel.h"
49 #include "vogleditor_statetreetextureitem.h"
50 #include "vogleditor_statetreeprogramitem.h"
51 #include "vogleditor_statetreeshaderitem.h"
52 #include "vogleditor_statetreeframebufferitem.h"
53 #include "vogleditor_qtextureexplorer.h"
54
55 #define VOGLEDITOR_DISABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), false);
56 #define VOGLEDITOR_ENABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), true);
57
58 //----------------------------------------------------------------------------------------------------------------------
59 // globals
60 //----------------------------------------------------------------------------------------------------------------------
61 static void *g_actual_libgl_module_handle;
62 static QString g_PROJECT_NAME = "Vogl Editor";
63
64 //----------------------------------------------------------------------------------------------------------------------
65 // vogl_get_proc_address_helper
66 //----------------------------------------------------------------------------------------------------------------------
67 static vogl_void_func_ptr_t vogl_get_proc_address_helper(const char *pName)
68 {
69    VOGL_FUNC_TRACER
70
71    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;
72
73    if ((!pFunc) && (GL_ENTRYPOINT(glXGetProcAddress)))
74       pFunc = reinterpret_cast<vogl_void_func_ptr_t>( GL_ENTRYPOINT(glXGetProcAddress)(reinterpret_cast<const GLubyte*>(pName)) );
75
76    return pFunc;
77 }
78
79
80 //----------------------------------------------------------------------------------------------------------------------
81 // load_gl
82 //----------------------------------------------------------------------------------------------------------------------
83 static bool load_gl()
84 {
85    VOGL_FUNC_TRACER
86
87    g_actual_libgl_module_handle = dlopen("libGL.so.1", RTLD_LAZY);
88    if (!g_actual_libgl_module_handle)
89    {
90       vogl_error_printf("%s: Failed loading libGL.so.1!\n", VOGL_FUNCTION_NAME);
91       return false;
92    }
93
94    GL_ENTRYPOINT(glXGetProcAddress) = reinterpret_cast<glXGetProcAddress_func_ptr_t>(dlsym(g_actual_libgl_module_handle, "glXGetProcAddress"));
95    if (!GL_ENTRYPOINT(glXGetProcAddress))
96    {
97       vogl_error_printf("%s: Failed getting address of glXGetProcAddress() from libGL.so.1!\n", VOGL_FUNCTION_NAME);
98       return false;
99    }
100
101    return true;
102 }
103
104 VoglEditor::VoglEditor(QWidget *parent) :
105    QMainWindow(parent),
106    ui(new Ui::VoglEditor),
107    m_statusLabel(NULL),
108    m_framebufferExplorer(NULL),
109    m_textureExplorer(NULL),
110    m_renderbufferExplorer(NULL),
111    m_programExplorer(NULL),
112    m_shaderExplorer(NULL),
113    m_timeline(NULL),
114    m_currentSnapshot(NULL),
115    m_pCurrentCallTreeItem(NULL),
116    m_pPlayButton(NULL),
117    m_pPauseButton(NULL),
118    m_pTrimButton(NULL),
119    m_pStopButton(NULL),
120    m_pTraceReader(NULL),
121    m_pApicallTreeModel(NULL)
122 {
123    ui->setupUi(this);
124
125    if (load_gl())
126    {
127       vogl_init_actual_gl_entrypoints(vogl_get_proc_address_helper);
128    }
129
130    m_statusLabel = new QLabel(ui->statusBar);
131    m_statusLabel->setBaseSize(150, 12);
132    ui->statusBar->addWidget(m_statusLabel, 1);
133
134    // setup framebuffer tab
135    QGridLayout* framebufferTab_layout = new QGridLayout;
136    m_framebufferExplorer = new vogleditor_QFramebufferExplorer(ui->framebufferTab);
137    framebufferTab_layout->addWidget(m_framebufferExplorer, 0, 0);
138    ui->framebufferTab->setLayout(framebufferTab_layout);
139
140    // setup texture tab
141    QGridLayout* textureTab_layout = new QGridLayout;
142    m_textureExplorer = new vogleditor_QTextureExplorer(ui->textureTab);
143    textureTab_layout->addWidget(m_textureExplorer, 0, 0);
144    ui->textureTab->setLayout(textureTab_layout);
145
146    // setup renderbuffer tab
147    QGridLayout* rbTab_layout = new QGridLayout;
148    m_renderbufferExplorer = new vogleditor_QTextureExplorer(ui->renderbufferTab);
149    rbTab_layout->addWidget(m_renderbufferExplorer, 0, 0);
150    ui->renderbufferTab->setLayout(rbTab_layout);
151
152    // setup program tab
153    QGridLayout* programTab_layout = new QGridLayout;
154    m_programExplorer = new vogleditor_QProgramExplorer(ui->programTab);
155    programTab_layout->addWidget(m_programExplorer, 0, 0);
156    ui->programTab->setLayout(programTab_layout);
157
158    // setup shader tab
159    QGridLayout* shaderTab_layout = new QGridLayout;
160    m_shaderExplorer = new vogleditor_QShaderExplorer(ui->shaderTab);
161    shaderTab_layout->addWidget(m_shaderExplorer, 0, 0);
162    ui->shaderTab->setLayout(shaderTab_layout);
163
164    // setup timeline
165    m_timeline = new vogleditor_QTimelineView();
166    m_timeline->setMinimumHeight(100);
167    ui->verticalLayout->addWidget(m_timeline);
168    ui->verticalLayout->removeWidget(ui->timelineViewPlaceholder);
169
170    // add buttons to toolbar
171    m_pPlayButton = new QToolButton(ui->mainToolBar);
172    m_pPlayButton->setText("Play trace");
173    m_pPlayButton->setEnabled(false);
174
175    m_pPauseButton = new QToolButton(ui->mainToolBar);
176    m_pPauseButton->setText("Pause");
177    m_pPauseButton->setEnabled(false);
178
179    m_pTrimButton = new QToolButton(ui->mainToolBar);
180    m_pTrimButton->setText("Trim");
181    m_pTrimButton->setEnabled(false);
182
183    m_pStopButton = new QToolButton(ui->mainToolBar);
184    m_pStopButton->setText("Stop");
185    m_pStopButton->setEnabled(false);
186
187    // Temporarily hide the other buttons (until asyncronous playback is supported)
188    m_pPauseButton->setVisible(false);
189    m_pTrimButton->setVisible(false);
190    m_pStopButton->setVisible(false);
191
192    ui->mainToolBar->addWidget(m_pPlayButton);
193    ui->mainToolBar->addWidget(m_pPauseButton);
194    ui->mainToolBar->addWidget(m_pTrimButton);
195    ui->mainToolBar->addWidget(m_pStopButton);
196
197    connect(m_pPlayButton, SIGNAL(clicked()), this, SLOT(playCurrentTraceFile()));
198    connect(m_pPauseButton, SIGNAL(clicked()), this, SLOT(pauseCurrentTraceFile()));
199    connect(m_pTrimButton, SIGNAL(clicked()), this, SLOT(trimCurrentTraceFile()));
200    connect(m_pStopButton, SIGNAL(clicked()), this, SLOT(stopCurrentTraceFile()));
201
202    connect(m_programExplorer, SIGNAL(program_edited(vogl_program_state*)), this, SLOT(on_program_edited(vogl_program_state*)));
203
204    reset_tracefile_ui();
205 }
206
207 VoglEditor::~VoglEditor()
208 {
209    close_trace_file();
210    delete ui;
211
212    if (m_textureExplorer != NULL)
213    {
214        delete m_textureExplorer;
215        m_textureExplorer = NULL;
216    }
217
218    if (m_renderbufferExplorer != NULL)
219    {
220        delete m_renderbufferExplorer;
221        m_renderbufferExplorer = NULL;
222    }
223
224    if (m_programExplorer != NULL)
225    {
226        delete m_programExplorer;
227        m_programExplorer = NULL;
228    }
229
230    if (m_shaderExplorer != NULL)
231    {
232        delete m_shaderExplorer;
233        m_shaderExplorer = NULL;
234    }
235 }
236
237 void VoglEditor::playCurrentTraceFile()
238 {
239     QCursor origCursor = cursor();
240     setCursor(Qt::WaitCursor);
241
242     // update UI
243     m_pPlayButton->setEnabled(false);
244     m_pPauseButton->setEnabled(true);
245     m_pTrimButton->setEnabled(true);
246     m_pStopButton->setEnabled(true);
247     m_statusLabel->clear();
248
249     if (m_traceReplayer.replay(m_pTraceReader, m_pApicallTreeModel->root(), NULL, 0, true))
250     {
251         // replay was successful
252         m_pPlayButton->setEnabled(true);
253         m_pPauseButton->setEnabled(false);
254         m_pTrimButton->setEnabled(false);
255         m_pStopButton->setEnabled(false);
256     }
257     else
258     {
259         m_statusLabel->setText("Failed to replay the trace.");
260     }
261
262     setCursor(origCursor);
263 }
264
265 void VoglEditor::pauseCurrentTraceFile()
266 {
267     if (m_traceReplayer.pause())
268     {
269        // update UI
270        m_pPlayButton->setEnabled(true);
271        m_pPauseButton->setEnabled(false);
272        m_pTrimButton->setEnabled(true);
273        m_pStopButton->setEnabled(true);
274        m_statusLabel->clear();
275     }
276     else
277     {
278         m_statusLabel->setText("Failed to pause the trace replay.");
279     }
280 }
281
282 void VoglEditor::trimCurrentTraceFile()
283 {
284     if (m_traceReplayer.trim())
285     {
286         m_statusLabel->clear();
287     }
288     else
289     {
290         m_statusLabel->setText("Failed to trim the trace replay.");
291     }
292 }
293
294 void VoglEditor::stopCurrentTraceFile()
295 {
296     if (m_traceReplayer.stop())
297     {
298         // update UI
299         m_pPlayButton->setEnabled(true);
300         m_pPauseButton->setEnabled(false);
301         m_pTrimButton->setEnabled(false);
302         m_pStopButton->setEnabled(false);
303         m_statusLabel->clear();
304     }
305     else
306     {
307         m_statusLabel->setText("Failed to stop the trace replay.");
308     }
309 }
310
311 void VoglEditor::on_actionE_xit_triggered()
312 {
313    qApp->quit();
314 }
315
316 void VoglEditor::on_action_Open_triggered()
317 {
318    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QString(),
319            tr("GLI Binary Files (*.bin);;JSON Files (*.json)"));
320
321    if (!fileName.isEmpty()) {
322       vogl::dynamic_string filename;
323       filename.set(fileName.toStdString().c_str());
324
325       if (open_trace_file(filename) == false) {
326           QMessageBox::critical(this, tr("Error"), tr("Could not open file"));
327           return;
328       }
329    }
330 }
331
332 void VoglEditor::on_action_Close_triggered()
333 {
334     close_trace_file();
335 }
336
337 void VoglEditor::close_trace_file()
338 {
339    if (m_pTraceReader != NULL)
340    {
341       m_pTraceReader->close();
342       m_pTraceReader = NULL;
343
344       setWindowTitle(g_PROJECT_NAME);
345
346       m_openFilename.clear();
347       m_backtraceToJsonMap.clear();
348       m_backtraceDoc.clear();
349       m_searchApicallResults.clear();
350
351       reset_tracefile_ui();
352
353       ui->treeView->setModel(NULL);
354       ui->machineInfoText->clear();
355       ui->backtraceText->clear();
356       m_timeline->setModel(NULL);
357       m_timeline->repaint();
358
359       if (m_pTimelineModel != NULL)
360       {
361           delete m_pTimelineModel;
362           m_pTimelineModel = NULL;
363       }
364    }
365 }
366
367 void VoglEditor::write_child_api_calls(vogleditor_apiCallTreeItem* pItem, FILE* pFile)
368 {
369     QString string = pItem->columnData(VOGL_ACTC_APICALL, Qt::DisplayRole).toString();
370     vogl_fwrite(string.toStdString().c_str(), 1, string.size(), pFile);
371     vogl_fwrite("\r\n", 1, 2, pFile);
372
373     for (int i = 0; i < pItem->childCount(); i++)
374     {
375         write_child_api_calls(pItem->child(i), pFile);
376     }
377 }
378
379 void VoglEditor::on_actionExport_API_Calls_triggered()
380 {
381     QString suggestedName = m_openFilename;
382
383     int lastIndex = suggestedName.lastIndexOf('-');
384     if (lastIndex != -1)
385     {
386         suggestedName = suggestedName.remove(lastIndex, suggestedName.size() - lastIndex);
387     }
388     suggestedName += "-ApiCalls.txt";
389
390     QString fileName = QFileDialog::getSaveFileName(this, tr("Export API Calls"), suggestedName,
391             tr("Text (*.txt)"));
392
393     if (!fileName.isEmpty())
394     {
395         vogl::dynamic_string filename;
396         filename.set(fileName.toStdString().c_str());
397
398         FILE* pFile = vogl_fopen(filename.c_str(), "w");
399         vogleditor_QApiCallTreeModel* pModel = static_cast<vogleditor_QApiCallTreeModel*>(ui->treeView->model());
400         vogleditor_apiCallTreeItem* pRoot = pModel->root();
401         for (int i = 0; i < pRoot->childCount(); i++)
402         {
403             write_child_api_calls(pRoot->child(i), pFile);
404         }
405         vogl_fclose(pFile);
406     }
407 }
408
409 //----------------------------------------------------------------------------------------------------------------------
410 // read_state_snapshot_from_trace
411 //----------------------------------------------------------------------------------------------------------------------
412 vogl_gl_state_snapshot* VoglEditor::read_state_snapshot_from_trace(vogl_trace_file_reader* pTrace_reader)
413 {
414    vogl_ctypes trace_gl_ctypes(pTrace_reader->get_sof_packet().m_pointer_sizes);
415
416    vogl_trace_packet keyframe_trace_packet(&trace_gl_ctypes);
417
418    pTrace_reader->seek_to_frame(0);
419
420    vogl_gl_state_snapshot *pSnapshot = NULL;
421    bool found_snapshot = false;
422    do
423    {
424       vogl_trace_file_reader::trace_file_reader_status_t read_status = pTrace_reader->read_next_packet();
425
426       if ((read_status != vogl_trace_file_reader::cOK) && (read_status != vogl_trace_file_reader::cEOF))
427       {
428          vogl_error_printf("%s: Failed reading from keyframe trace file!\n", VOGL_FUNCTION_NAME);
429          return NULL;
430       }
431
432       if ((read_status == vogl_trace_file_reader::cEOF) || (pTrace_reader->get_packet_type() == cTSPTEOF))
433       {
434          vogl_error_printf("%s: Failed finding state snapshot in keyframe file!\n", VOGL_FUNCTION_NAME);
435          return NULL;
436       }
437
438       if (pTrace_reader->get_packet_type() != cTSPTGLEntrypoint)
439          continue;
440
441       if (!keyframe_trace_packet.deserialize(pTrace_reader->get_packet_buf().get_ptr(), pTrace_reader->get_packet_buf().size(), false))
442       {
443          vogl_error_printf("%s: Failed parsing GL entrypoint packet in keyframe file\n", VOGL_FUNCTION_NAME);
444          return NULL;
445       }
446
447       const vogl_trace_gl_entrypoint_packet *pGL_packet = &pTrace_reader->get_packet<vogl_trace_gl_entrypoint_packet>();
448       gl_entrypoint_id_t entrypoint_id = static_cast<gl_entrypoint_id_t>(pGL_packet->m_entrypoint_id);
449
450       if (vogl_is_swap_buffers_entrypoint(entrypoint_id) || vogl_is_draw_entrypoint(entrypoint_id) || vogl_is_make_current_entrypoint(entrypoint_id))
451       {
452          vogl_error_printf("Failed finding state snapshot in keyframe file!\n");
453          return NULL;
454       }
455
456       switch (entrypoint_id)
457       {
458          case VOGL_ENTRYPOINT_glInternalTraceCommandRAD:
459          {
460             GLuint cmd = keyframe_trace_packet.get_param_value<GLuint>(0);
461             GLuint size = keyframe_trace_packet.get_param_value<GLuint>(1); VOGL_NOTE_UNUSED(size);
462
463             if (cmd == cITCRKeyValueMap)
464             {
465                key_value_map &kvm = keyframe_trace_packet.get_key_value_map();
466
467                dynamic_string cmd_type(kvm.get_string("command_type"));
468                if (cmd_type == "state_snapshot")
469                {
470                   dynamic_string id(kvm.get_string("binary_id"));
471                   if (id.is_empty())
472                   {
473                      vogl_error_printf("%s: Missing binary_id field in glInternalTraceCommandRAD key_valye_map command type: \"%s\"\n", VOGL_FUNCTION_NAME, cmd_type.get_ptr());
474                      return NULL;
475                   }
476
477                   uint8_vec snapshot_data;
478                   {
479                      timed_scope ts("get_multi_blob_manager().get");
480                      if (!pTrace_reader->get_multi_blob_manager().get(id, snapshot_data) || (snapshot_data.is_empty()))
481                      {
482                         vogl_error_printf("%s: Failed reading snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
483                         return NULL;
484                      }
485                   }
486
487                   vogl_message_printf("%s: Deserializing state snapshot \"%s\", %u bytes\n", VOGL_FUNCTION_NAME, id.get_ptr(), snapshot_data.size());
488
489                   json_document doc;
490                   {
491                      timed_scope ts("doc.binary_deserialize");
492                      if (!doc.binary_deserialize(snapshot_data) || (!doc.get_root()))
493                      {
494                         vogl_error_printf("%s: Failed deserializing JSON snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
495                         return NULL;
496                      }
497                   }
498
499                   pSnapshot = vogl_new(vogl_gl_state_snapshot);
500
501                   timed_scope ts("pSnapshot->deserialize");
502                   if (!pSnapshot->deserialize(*doc.get_root(), pTrace_reader->get_multi_blob_manager(), &trace_gl_ctypes))
503                   {
504                      vogl_delete(pSnapshot);
505                      pSnapshot = NULL;
506
507                      vogl_error_printf("%s: Failed deserializing snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
508                      return NULL;
509                   }
510
511                   found_snapshot = true;
512                }
513             }
514
515             break;
516          }
517          default: break;
518       }
519
520    } while (!found_snapshot);
521
522    return pSnapshot;
523 }
524
525
526 bool VoglEditor::open_trace_file(dynamic_string filename)
527 {
528    QCursor origCursor = this->cursor();
529    this->setCursor(Qt::WaitCursor);
530
531    vogl_loose_file_blob_manager file_blob_manager;
532    dynamic_string keyframe_trace_path(file_utils::get_pathname(filename.get_ptr()));
533    file_blob_manager.init(cBMFReadable, keyframe_trace_path.get_ptr());
534
535    dynamic_string actual_keyframe_filename;
536
537    vogl_trace_file_reader* tmpReader = vogl_open_trace_file(filename, actual_keyframe_filename, NULL);
538
539    if (tmpReader == NULL)
540    {
541       m_statusLabel->setText("Failed to open: ");
542       m_statusLabel->setText(m_statusLabel->text().append(filename.c_str()));
543       this->setCursor(origCursor);
544       return false;
545    }
546    else
547    {
548        m_statusLabel->clear();
549    }
550
551    // now that we know the new trace file can be opened,
552    // close the old one, and update the trace reader
553    close_trace_file();
554    m_pTraceReader = tmpReader;
555
556    m_pApicallTreeModel = new vogleditor_QApiCallTreeModel(m_pTraceReader);
557    ui->treeView->setModel(m_pApicallTreeModel);
558
559    if (ui->treeView->selectionModel() != NULL)
560    {
561       //connect(ui->treeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(on_treeView_selectionChanged(const QItemSelection&, const QItemSelection&)));
562       connect(ui->treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(on_treeView_currentChanged(const QModelIndex &, const QModelIndex &)));
563    }
564
565    if (m_pApicallTreeModel->hasChildren())
566    {
567       ui->treeView->setExpanded(m_pApicallTreeModel->index(0,0), true);
568       ui->treeView->setCurrentIndex(m_pApicallTreeModel->index(0,0));
569    }
570
571    int flagsColumnWidth = 30;
572    ui->treeView->header()->setMinimumSectionSize(flagsColumnWidth);
573    ui->treeView->header()->moveSection(VOGL_ACTC_FLAGS, 0);
574    ui->treeView->setColumnWidth(VOGL_ACTC_FLAGS, flagsColumnWidth);
575
576    int width = ui->treeView->width() - flagsColumnWidth - 30; // subtract a little extra for the scrollbar width
577    ui->treeView->setColumnWidth(VOGL_ACTC_APICALL, width * 0.7);
578    ui->treeView->setColumnWidth(VOGL_ACTC_INDEX, width * 0.15);
579    ui->treeView->setColumnWidth(VOGL_ACTC_DURATION, width * 0.15);
580
581    ui->searchTextBox->setEnabled(true);
582    ui->searchPrevButton->setEnabled(true);
583    ui->searchNextButton->setEnabled(true);
584
585    ui->action_Close->setEnabled(true);
586    ui->actionExport_API_Calls->setEnabled(true);
587
588    ui->prevSnapshotButton->setEnabled(true);
589    ui->nextSnapshotButton->setEnabled(true);
590    ui->prevDrawcallButton->setEnabled(true);
591    ui->nextDrawcallButton->setEnabled(true);
592
593    m_backtraceToJsonMap.clear();
594    m_backtraceDoc.clear();
595
596     // Extract backtrace map and machine info from trace archive
597     if (m_pTraceReader->get_archive_blob_manager().is_initialized())
598     {
599         // backtrace
600         uint8_vec backtrace_data;
601         bool bBacktraceVisible = false;
602         if (m_pTraceReader->get_archive_blob_manager().does_exist(VOGL_TRACE_ARCHIVE_BACKTRACE_MAP_ADDRS_FILENAME))
603         {
604             //$ TODO mikesart: read MAP_SYMS data here when symbols have been resolved.
605             if (m_pTraceReader->get_archive_blob_manager().get(VOGL_TRACE_ARCHIVE_BACKTRACE_MAP_ADDRS_FILENAME, backtrace_data))
606             {
607                 json_node* pRoot = m_backtraceDoc.get_root();
608                 if (m_backtraceDoc.deserialize((const char*)backtrace_data.get_ptr(), backtrace_data.size()))
609                 {
610                     bBacktraceVisible = pRoot->size() > 0;
611                     for (uint i = 0; i < pRoot->size(); i++)
612                     {
613                         json_node* pChild = pRoot->get_child(i);
614                         uint32 index = 0;
615                         VOGL_ASSERT("Backtrace node does not have an 'index' child" && pChild != NULL && pChild->get_value_as_uint32("index", index));
616                         if (pChild != NULL && pChild->get_value_as_uint32("index", index))
617                         {
618                             m_backtraceToJsonMap.insert(index, pChild);
619                         }
620                     }
621                 }
622             }
623         }
624
625         QList<int> backtraceSplitterSizes = ui->splitter_3->sizes();
626         int backtraceSplitterTotalSize = backtraceSplitterSizes[0] + backtraceSplitterSizes[1];
627         QList<int> newBacktraceSplitterSizes;
628         if (!bBacktraceVisible)
629         {
630             newBacktraceSplitterSizes.append(backtraceSplitterTotalSize);
631             newBacktraceSplitterSizes.append(0);
632             ui->splitter_3->setSizes(newBacktraceSplitterSizes);
633         }
634         else
635         {
636             newBacktraceSplitterSizes << (backtraceSplitterTotalSize * 0.75)
637                                       << (backtraceSplitterTotalSize * 0.25);
638             ui->splitter_3->setSizes(newBacktraceSplitterSizes);
639         }
640
641         // machine info
642         displayMachineInfo();
643    }
644
645    m_openFilename = filename.c_str();
646
647    setWindowTitle(m_openFilename + " - " + g_PROJECT_NAME);
648
649    ui->tabWidget->setCurrentWidget(ui->framebufferTab);
650
651    // update toolbar
652    m_pPlayButton->setEnabled(true);
653    m_pPauseButton->setEnabled(false);
654    m_pTrimButton->setEnabled(false);
655    m_pStopButton->setEnabled(false);
656
657    // timeline
658    m_pTimelineModel = new vogleditor_apiCallTimelineModel(m_pApicallTreeModel->root());
659    m_timeline->setModel(m_pTimelineModel);
660    m_timeline->repaint();
661
662    this->setCursor(origCursor);
663    return true;
664 }
665
666 void VoglEditor::displayMachineInfoHelper(QString prefix, const QString& sectionKeyStr, const vogl::json_value& value, QString& rMachineInfoStr)
667 {
668     if (value.is_array())
669     {
670         const json_node* pNode = value.get_node_ptr();
671         for (uint element = 0; element < pNode->size(); element++)
672         {
673             dynamic_string elementStr = pNode->get_value(element).as_string();
674
675             elementStr = elementStr.replace("\n", "\n\t");
676
677             rMachineInfoStr += "\t";
678             rMachineInfoStr += elementStr.get_ptr();
679             rMachineInfoStr += "\n";
680         }
681
682         rMachineInfoStr += "\n";
683     }
684     else if (value.is_node())
685     {
686         // Check if this is the modoule list.
687         bool is_module_list = (sectionKeyStr == "module_list");
688         const json_node* pNode = value.get_node_ptr();
689
690         for (uint i = 0; i < pNode->size(); i++)
691         {
692             dynamic_string key = pNode->get_key(i);
693             const json_value &value2 = pNode->get_value(i);
694
695             rMachineInfoStr += prefix;
696             // If it's the module list, then the key is the filename and we want to display that last.
697             if (!is_module_list)
698                 rMachineInfoStr += key.c_str();
699
700             if (value2.is_array())
701             {
702                 const json_node* pNode2 = value2.get_node_ptr();
703
704                 // If this it module_list, then we get these items: base address, address size, uuid
705                 // Check in btrace_get_machine_info() to see what's written there.
706                 for (uint element = 0; element < pNode2->size(); element++)
707                 {
708                     const json_value &json_val = pNode2->get_value(element);
709
710                     if (json_val.is_string())
711                     {
712                         dynamic_string str = pNode2->get_value(element).as_string();
713                         rMachineInfoStr += str.c_str();
714                     }
715                     else
716                     {
717                         dynamic_string buf;
718                         buf.format("%" PRIx64, json_val.as_uint64());
719                         rMachineInfoStr += buf.c_str();
720                     }
721
722                     rMachineInfoStr += "\t";
723                 }
724             }
725             else
726             {
727                 rMachineInfoStr += ": ";
728                 rMachineInfoStr += value2.as_string_ptr();
729             }
730
731             // Display the filename if this is the module_list.
732             if (is_module_list)
733                 rMachineInfoStr += key.c_str();
734             rMachineInfoStr += "\n";
735         }
736
737         rMachineInfoStr += "\n";
738     }
739     else if (value.is_string())
740     {
741         rMachineInfoStr += value.as_string_ptr();
742     }
743     else
744     {
745         rMachineInfoStr += value.as_string_ptr();
746     }
747 }
748
749 void VoglEditor::displayMachineInfo()
750 {
751     VOGL_ASSERT(m_pTraceReader != NULL);
752
753     bool bMachineInfoVisible = false;
754     if (m_pTraceReader->get_archive_blob_manager().does_exist(VOGL_TRACE_ARCHIVE_MACHINE_INFO_FILENAME))
755     {
756         uint8_vec machine_info_data;
757         if (m_pTraceReader->get_archive_blob_manager().get(VOGL_TRACE_ARCHIVE_MACHINE_INFO_FILENAME, machine_info_data))
758         {
759             bMachineInfoVisible = true;
760             json_document doc;
761             json_node *pRoot = doc.get_root();
762             if (doc.deserialize((const char*)machine_info_data.get_ptr(), machine_info_data.size()))
763             {
764                 QString text;
765                 for (uint i = 0; i < pRoot->size(); i++)
766                 {
767                     dynamic_string sectionKeyStr = pRoot->get_key(i);
768                     text += pRoot->get_key(i).c_str();
769                     text += "\n";
770
771                     QString keyStr = sectionKeyStr.c_str();
772                     displayMachineInfoHelper("\t", keyStr, pRoot->get_value(i), text);
773                 }
774
775                 ui->machineInfoText->setText(text);
776             }
777         }
778     }
779
780     if (bMachineInfoVisible)
781     {
782         if (ui->tabWidget->indexOf(ui->machineInfoTab) == -1)
783         {
784             // unhide the tab
785             ui->tabWidget->insertTab(0, ui->machineInfoTab, "Machine Info");
786         }
787         else
788         {
789             VOGLEDITOR_ENABLE_TAB(ui->machineInfoTab);
790         }
791     }
792     else
793     {
794         ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->machineInfoTab));
795     }
796 }
797
798 void VoglEditor::reset_tracefile_ui()
799 {
800     ui->action_Close->setEnabled(false);
801     ui->actionExport_API_Calls->setEnabled(false);
802
803     ui->prevSnapshotButton->setEnabled(false);
804     ui->nextSnapshotButton->setEnabled(false);
805     ui->prevDrawcallButton->setEnabled(false);
806     ui->nextDrawcallButton->setEnabled(false);
807     ui->searchTextBox->clear();
808     ui->searchTextBox->setEnabled(false);
809     ui->searchPrevButton->setEnabled(false);
810     ui->searchNextButton->setEnabled(false);
811
812     m_statusLabel->clear();
813     m_pPlayButton->setEnabled(false);
814     m_pPauseButton->setEnabled(false);
815     m_pTrimButton->setEnabled(false);
816     m_pStopButton->setEnabled(false);
817
818     VOGLEDITOR_DISABLE_TAB(ui->machineInfoTab);
819
820     reset_snapshot_ui();
821 }
822
823 void VoglEditor::reset_snapshot_ui()
824 {
825     m_currentSnapshot = NULL;
826
827     m_framebufferExplorer->clear();
828     m_textureExplorer->clear();
829     m_renderbufferExplorer->clear();
830     m_programExplorer->clear();
831     m_shaderExplorer->clear();
832
833     ui->stateTreeView->setModel(NULL);
834
835     QWidget* pCurrentTab = ui->tabWidget->currentWidget();
836
837     VOGLEDITOR_DISABLE_TAB(ui->stateTab);
838     VOGLEDITOR_DISABLE_TAB(ui->framebufferTab);
839     VOGLEDITOR_DISABLE_TAB(ui->programTab);
840     VOGLEDITOR_DISABLE_TAB(ui->shaderTab);
841     VOGLEDITOR_DISABLE_TAB(ui->textureTab);
842     VOGLEDITOR_DISABLE_TAB(ui->renderbufferTab);
843
844     ui->tabWidget->setCurrentWidget(pCurrentTab);
845 }
846
847 /// 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
848 /// and also has no children. The pMostRecentSnapshot parameter will be updated to point to the desired snapshot.
849 /// This function does not follow a traditional DFS search because we need to find the desired snapshot then return the one before it.
850 /// An alternative approach would be to keep a stack of the found snapshots, or even to build up that stack / list as the user
851 /// generates new snapshots.
852 vogleditor_gl_state_snapshot* VoglEditor::findMostRecentSnapshot_helper(vogleditor_apiCallTreeItem* pItem, vogleditor_gl_state_snapshot*& pMostRecentSnapshot, const vogleditor_gl_state_snapshot* pCurSnapshot)
853 {
854     // check if this item has a snapshot shot
855     if (pItem->has_snapshot())
856     {
857         vogleditor_gl_state_snapshot* pTmp = pItem->get_snapshot();
858         if (pTmp == pCurSnapshot)
859         {
860             // if we've reached the item with the current snapshot, we want to return the previous snapshot.
861             return pTmp;
862         }
863         else
864         {
865             // update most recent snapshot
866             pMostRecentSnapshot = pTmp;
867         }
868     }
869
870     for (int i = 0; i < pItem->childCount(); i++)
871     {
872         vogleditor_gl_state_snapshot* pTmp = findMostRecentSnapshot_helper(pItem->child(i), pMostRecentSnapshot, pCurSnapshot);
873         if (pTmp != NULL)
874         {
875             if (pTmp == pCurSnapshot)
876             {
877                 // if we've reached the item with the current snapshot, we want to return the previous snapshot.
878                 return pTmp;
879             }
880             else
881             {
882                 // update most recent snapshot
883                 pMostRecentSnapshot = pTmp;
884             }
885         }
886     }
887
888     return NULL;
889 }
890
891 /// This function exists just to simplify the interaction with the helper, so that there no confusion between
892 /// whether the returned value, or passed in reference parameter should be used as the most recent snapshot.
893 /// It will either return NULL if there is no recent snapshot (which should only happen for the very first snapshot
894 /// in a trace), or a pointer to a valid snapshot.
895 vogleditor_gl_state_snapshot* VoglEditor::findMostRecentSnapshot(vogleditor_apiCallTreeItem* pItem, const vogleditor_gl_state_snapshot* pCurSnapshot)
896 {
897     vogleditor_gl_state_snapshot* pMostRecentSnapshot = NULL;
898     findMostRecentSnapshot_helper(pItem, pMostRecentSnapshot, pCurSnapshot);
899     return pMostRecentSnapshot;
900 }
901
902 void VoglEditor::update_ui_for_snapshot(vogleditor_gl_state_snapshot* pStateSnapshot)
903 {
904    if (pStateSnapshot == NULL)
905    {
906       reset_snapshot_ui();
907       return;
908    }
909
910    if (pStateSnapshot->is_valid() == false)
911    {
912        reset_snapshot_ui();
913        return;
914    }
915
916    if (m_currentSnapshot == pStateSnapshot)
917    {
918        // no need to update if it is the same snapshot
919        return;
920    }
921
922    m_currentSnapshot = pStateSnapshot;
923
924    if (ui->stateTreeView->model() != NULL)
925    {
926       if (static_cast<vogleditor_QStateTreeModel*>(ui->stateTreeView->model())->get_snapshot() == m_currentSnapshot)
927       {
928          // displaying the same snapshot, return
929          return;
930       }
931    }
932
933    QCursor origCursor = this->cursor();
934    this->setCursor(Qt::WaitCursor);
935
936    // state viewer
937    vogleditor_QStateTreeModel* pStateModel = new vogleditor_QStateTreeModel(NULL);
938
939    vogleditor_QApiCallTreeModel* pTreeModel = static_cast<vogleditor_QApiCallTreeModel*>(ui->treeView->model());
940    vogleditor_gl_state_snapshot* pBaseSnapshot = findMostRecentSnapshot(pTreeModel->root(), m_currentSnapshot);
941    pStateModel->set_diff_base_snapshot(pBaseSnapshot);
942
943    pStateModel->set_snapshot(pStateSnapshot);
944
945    ui->stateTreeView->setModel(pStateModel);
946    ui->stateTreeView->expandToDepth(1);
947    ui->stateTreeView->setColumnWidth(0, ui->stateTreeView->width() * 0.5);
948
949    VOGLEDITOR_ENABLE_TAB(ui->stateTab);
950
951    if (pStateSnapshot->get_contexts().size() > 0)
952    {
953        vogl_trace_ptr_value curContextHandle = pStateSnapshot->get_cur_trace_context();
954        if (curContextHandle != 0)
955        {
956            vogl_context_snapshot* pContext = pStateSnapshot->get_context(curContextHandle);
957
958            // textures
959            vogl_gl_object_state_ptr_vec textureObjects;
960            pContext->get_all_objects_of_category(cGLSTTexture, textureObjects);
961            m_textureExplorer->set_texture_objects(textureObjects);
962
963            GLuint curActiveTextureUnit = pContext->get_general_state().get_value<GLuint>(GL_ACTIVE_TEXTURE);
964            if (curActiveTextureUnit >= GL_TEXTURE0 && curActiveTextureUnit < (GL_TEXTURE0 + pContext->get_context_info().get_max_texture_image_units()))
965            {
966                GLuint cur2DBinding = pContext->get_general_state().get_value<GLuint>(GL_TEXTURE_2D_BINDING_EXT, curActiveTextureUnit - GL_TEXTURE0);
967                displayTexture(cur2DBinding, false);
968            }
969
970            // renderbuffers
971            vogl_gl_object_state_ptr_vec renderbufferObjects;
972            pContext->get_all_objects_of_category(cGLSTRenderbuffer, renderbufferObjects);
973            m_renderbufferExplorer->set_texture_objects(renderbufferObjects);
974            if (renderbufferObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->renderbufferTab); }
975
976            // framebuffer
977            vogl_gl_object_state_ptr_vec framebufferObjects;
978            pContext->get_all_objects_of_category(cGLSTFramebuffer, framebufferObjects);
979            m_framebufferExplorer->set_framebuffer_objects(framebufferObjects, *pContext, pStateSnapshot->get_default_framebuffer());
980            GLuint64 curDrawFramebuffer = pContext->get_general_state().get_value<GLuint64>(GL_DRAW_FRAMEBUFFER_BINDING);
981            displayFramebuffer(curDrawFramebuffer, false);
982
983            // programs
984            vogl_gl_object_state_ptr_vec programObjects;
985            pContext->get_all_objects_of_category(cGLSTProgram, programObjects);
986            m_programExplorer->set_program_objects(programObjects);
987            GLuint64 curProgram = pContext->get_general_state().get_value<GLuint64>(GL_CURRENT_PROGRAM);
988            m_programExplorer->set_active_program(curProgram);
989            if (programObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->programTab); }
990
991            // shaders
992            vogl_gl_object_state_ptr_vec shaderObjects;
993            pContext->get_all_objects_of_category(cGLSTShader, shaderObjects);
994            m_shaderExplorer->set_shader_objects(shaderObjects);
995            if (curProgram != 0)
996            {
997                for (vogl_gl_object_state_ptr_vec::iterator iter = programObjects.begin(); iter != programObjects.end(); iter++)
998                {
999                    if ((*iter)->get_snapshot_handle() == curProgram)
1000                    {
1001                        vogl_program_state* pProgramState = static_cast<vogl_program_state*>(*iter);
1002                        if (pProgramState->get_attached_shaders().size() > 0)
1003                        {
1004                            uint curShader = pProgramState->get_attached_shaders()[0];
1005                            m_shaderExplorer->set_active_shader(curShader);
1006                        }
1007                        break;
1008                    }
1009                }
1010            }
1011            if (shaderObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->shaderTab); }
1012        }
1013    }
1014
1015    this->setCursor(origCursor);
1016 }
1017
1018 void VoglEditor::on_stateTreeView_clicked(const QModelIndex &index)
1019 {
1020    vogleditor_stateTreeItem* pStateItem = static_cast<vogleditor_stateTreeItem*>(index.internalPointer());
1021    if (pStateItem == NULL)
1022    {
1023       return;
1024    }
1025
1026    switch(pStateItem->getStateType())
1027    {
1028    case vogleditor_stateTreeItem::cTEXTURE:
1029    {
1030       vogleditor_stateTreeTextureItem* pTextureItem = static_cast<vogleditor_stateTreeTextureItem*>(pStateItem);
1031       if (pTextureItem == NULL)
1032       {
1033          break;
1034       }
1035
1036       displayTexture(pTextureItem->get_texture_state()->get_snapshot_handle(), true);
1037
1038       break;
1039    }
1040    case vogleditor_stateTreeItem::cPROGRAM:
1041    {
1042       vogleditor_stateTreeProgramItem* pProgramItem = static_cast<vogleditor_stateTreeProgramItem*>(pStateItem);
1043       if (pProgramItem == NULL)
1044       {
1045          break;
1046       }
1047
1048       displayProgram(pProgramItem->get_current_state()->get_snapshot_handle(), true);
1049
1050       break;
1051    }
1052    case vogleditor_stateTreeItem::cSHADER:
1053    {
1054       vogleditor_stateTreeShaderItem* pShaderItem = static_cast<vogleditor_stateTreeShaderItem*>(pStateItem);
1055       if (pShaderItem == NULL)
1056       {
1057          break;
1058       }
1059
1060       displayShader(pShaderItem->get_current_state()->get_snapshot_handle(), true);
1061
1062       break;
1063    }
1064    case vogleditor_stateTreeItem::cFRAMEBUFFER:
1065    {
1066       vogleditor_stateTreeFramebufferItem* pFramebufferItem = static_cast<vogleditor_stateTreeFramebufferItem*>(pStateItem);
1067       if (pFramebufferItem == NULL)
1068       {
1069          break;
1070       }
1071
1072       displayFramebuffer(pFramebufferItem->get_framebuffer_state()->get_snapshot_handle(), true);
1073
1074       break;
1075    }
1076    case vogleditor_stateTreeItem::cDEFAULT:
1077    {
1078       return;
1079    }
1080    }
1081 }
1082
1083 bool VoglEditor::displayShader(GLuint64 shaderHandle, bool bBringTabToFront)
1084 {
1085     bool bDisplayed = false;
1086     if (m_shaderExplorer->set_active_shader(shaderHandle))
1087     {
1088         if (bBringTabToFront)
1089         {
1090             ui->tabWidget->setCurrentWidget(ui->shaderTab);
1091         }
1092     }
1093
1094     return bDisplayed;
1095 }
1096
1097 void VoglEditor::displayProgram(GLuint64 programHandle, bool bBringTabToFront)
1098 {
1099     if (m_programExplorer->set_active_program(programHandle))
1100     {
1101         if (bBringTabToFront)
1102         {
1103             ui->tabWidget->setCurrentWidget(ui->programTab);
1104         }
1105     }
1106 }
1107
1108 void VoglEditor::displayFramebuffer(GLuint64 framebufferHandle, bool bBringTabToFront)
1109 {
1110     bool bDisplayedFBO = m_framebufferExplorer->set_active_framebuffer(framebufferHandle);
1111
1112     if (bDisplayedFBO)
1113     {
1114         VOGLEDITOR_ENABLE_TAB(ui->framebufferTab);
1115         if (bBringTabToFront)
1116         {
1117             ui->tabWidget->setCurrentWidget(ui->framebufferTab);
1118         }
1119     }
1120 }
1121
1122 bool VoglEditor::displayTexture(GLuint64 textureHandle, bool bBringTabToFront)
1123 {
1124     bool bDisplayedTexture = m_textureExplorer->set_active_texture(textureHandle);
1125
1126     if (bDisplayedTexture)
1127     {
1128         VOGLEDITOR_ENABLE_TAB(ui->textureTab);
1129         if (bBringTabToFront)
1130         {
1131             ui->tabWidget->setCurrentWidget(ui->textureTab);
1132         }
1133     }
1134
1135     return bDisplayedTexture;
1136 }
1137
1138 void VoglEditor::on_treeView_currentChanged(const QModelIndex & current, const QModelIndex & previous)
1139 {
1140     VOGL_NOTE_UNUSED(previous);
1141     onApiCallSelected(current, false);
1142 }
1143
1144 void VoglEditor::on_treeView_clicked(const QModelIndex &index)
1145 {
1146     onApiCallSelected(index, true);
1147 }
1148
1149 void VoglEditor::onApiCallSelected(const QModelIndex &index, bool bAllowStateSnapshot)
1150 {
1151     vogleditor_apiCallTreeItem* pCallTreeItem = static_cast<vogleditor_apiCallTreeItem*>(index.internalPointer());
1152     if (pCallTreeItem == NULL)
1153     {
1154        return;
1155     }
1156
1157     vogleditor_frameItem* pFrameItem = pCallTreeItem->frameItem();
1158     vogleditor_apiCallItem* pApiCallItem = pCallTreeItem->apiCallItem();
1159
1160     if (bAllowStateSnapshot && pCallTreeItem == m_pCurrentCallTreeItem)
1161     {
1162         // we can only get snapshots for specific API calls
1163         if (pApiCallItem != NULL && pApiCallItem->needs_snapshot())
1164         {
1165            // get the snapshot after the current api call
1166            vogleditor_gl_state_snapshot* pNewSnapshot = NULL;
1167            QCursor origCursor = cursor();
1168            setCursor(Qt::WaitCursor);
1169            m_traceReplayer.replay(m_pTraceReader, m_pApicallTreeModel->root(), &pNewSnapshot, pApiCallItem->globalCallIndex(), false);
1170            setCursor(origCursor);
1171            pCallTreeItem->set_snapshot(pNewSnapshot);
1172         }
1173     }
1174
1175     update_ui_for_snapshot(pCallTreeItem->get_snapshot());
1176
1177     if (pApiCallItem != NULL && m_pCurrentCallTreeItem != pCallTreeItem)
1178     {
1179         if (m_backtraceToJsonMap.size() > 0)
1180         {
1181             QString tmp;
1182             json_node* pBacktraceNode = m_backtraceToJsonMap[(uint)pApiCallItem->backtraceHashIndex()];
1183             if (pBacktraceNode != NULL)
1184             {
1185                 json_node* pAddrs = pBacktraceNode->find_child_array("addrs");
1186                 json_node* pSyms = pBacktraceNode->find_child_array("syms");
1187
1188                 for (uint i = 0; i < pAddrs->size(); i++)
1189                 {
1190                     tmp += pAddrs->get_value(i).as_string_ptr();
1191                     if (pSyms)
1192                     {
1193                         tmp += "\t";
1194                         tmp += pSyms->get_value(i).as_string_ptr();
1195                     }
1196                     tmp += "\n";
1197                 }
1198             }
1199             ui->backtraceText->setText(tmp);
1200         }
1201     }
1202
1203     if (pApiCallItem != NULL)
1204     {
1205         m_timeline->setCurrentApiCall(pApiCallItem->globalCallIndex());
1206     }
1207
1208     if (pFrameItem != NULL)
1209     {
1210        m_timeline->setCurrentFrame(pFrameItem->frameNumber());
1211     }
1212
1213     m_timeline->repaint();
1214
1215     m_pCurrentCallTreeItem = pCallTreeItem;
1216 }
1217
1218 void VoglEditor::selectApicallModelIndex(QModelIndex index, bool scrollTo, bool select)
1219 {
1220     // make sure the index is visible
1221     QModelIndex parentIndex = index.parent();
1222     while (parentIndex.isValid())
1223     {
1224         if (ui->treeView->isExpanded(parentIndex) == false)
1225         {
1226             ui->treeView->expand(parentIndex);
1227         }
1228         parentIndex = parentIndex.parent();
1229     }
1230
1231     // scroll to the index
1232     if (scrollTo)
1233     {
1234         ui->treeView->scrollTo(index);
1235     }
1236
1237     // select the index
1238     if (select)
1239     {
1240         ui->treeView->setCurrentIndex(index);
1241     }
1242
1243     if (m_searchApicallResults.size() > 0 && !ui->searchTextBox->text().isEmpty())
1244     {
1245         QItemSelectionModel* pSelection = ui->treeView->selectionModel();
1246         for (int i = 0; i < m_searchApicallResults.size(); i++)
1247         {
1248             pSelection->select(m_searchApicallResults[i], QItemSelectionModel::Select | QItemSelectionModel::Rows);
1249         }
1250         ui->treeView->setSelectionModel(pSelection);
1251     }
1252 }
1253
1254 void VoglEditor::on_searchTextBox_textChanged(const QString &searchText)
1255 {
1256     QModelIndex curSearchIndex = ui->treeView->currentIndex();
1257     if (curSearchIndex.isValid() == false)
1258     {
1259         return;
1260     }
1261
1262     // store original background color of the search text box so that it can be turned to red and later restored.
1263     static const QColor sOriginalTextBoxBackground = ui->searchTextBox->palette().base().color();
1264
1265     // clear previous items
1266     QItemSelectionModel* pSelection = ui->treeView->selectionModel();
1267     if (pSelection != NULL)
1268     {
1269         for (int i = 0; i < m_searchApicallResults.size(); i++)
1270         {
1271             pSelection->select(m_searchApicallResults[i], QItemSelectionModel::Clear | QItemSelectionModel::Rows);
1272         }
1273         ui->treeView->setSelectionModel(pSelection);
1274     }
1275
1276     // find new matches
1277     m_searchApicallResults.clear();
1278     if (m_pApicallTreeModel != NULL)
1279     {
1280         m_searchApicallResults = m_pApicallTreeModel->find_search_matches(searchText);
1281     }
1282
1283     // if there are matches, restore the textbox background to its original color
1284     if (m_searchApicallResults.size() > 0)
1285     {
1286         QPalette palette(ui->searchTextBox->palette());
1287         palette.setColor(QPalette::Base, sOriginalTextBoxBackground);
1288         ui->searchTextBox->setPalette(palette);
1289     }
1290
1291     // select new items
1292     if (!searchText.isEmpty())
1293     {
1294         if (m_searchApicallResults.size() > 0)
1295         {
1296             // scroll to the first result, but don't select it
1297             selectApicallModelIndex(m_searchApicallResults[0], true, false);
1298         }
1299         else
1300         {
1301             // no items were found, so set the textbox background to red
1302             QPalette palette(ui->searchTextBox->palette());
1303             palette.setColor(QPalette::Base, Qt::red);
1304             ui->searchTextBox->setPalette(palette);
1305         }
1306     }
1307 }
1308
1309 void VoglEditor::on_searchNextButton_clicked()
1310 {
1311     if (m_pApicallTreeModel != NULL)
1312     {
1313         QModelIndex index = m_pApicallTreeModel->find_next_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
1314         selectApicallModelIndex(index, true, true);
1315     }
1316 }
1317
1318 void VoglEditor::on_searchPrevButton_clicked()
1319 {
1320     if (m_pApicallTreeModel != NULL)
1321     {
1322         QModelIndex index = m_pApicallTreeModel->find_prev_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
1323         selectApicallModelIndex(index, true, true);
1324     }
1325 }
1326
1327 void VoglEditor::on_prevSnapshotButton_clicked()
1328 {
1329     if (m_pApicallTreeModel != NULL)
1330     {
1331         vogleditor_apiCallTreeItem* pPrevItemWithSnapshot = m_pApicallTreeModel->find_prev_snapshot(m_pCurrentCallTreeItem);
1332         selectApicallModelIndex(m_pApicallTreeModel->indexOf(pPrevItemWithSnapshot), true, true);
1333         ui->treeView->setFocus();
1334     }
1335 }
1336
1337 void VoglEditor::on_nextSnapshotButton_clicked()
1338 {
1339     if (m_pApicallTreeModel != NULL)
1340     {
1341         vogleditor_apiCallTreeItem* pNextItemWithSnapshot = m_pApicallTreeModel->find_next_snapshot(m_pCurrentCallTreeItem);
1342         selectApicallModelIndex(m_pApicallTreeModel->indexOf(pNextItemWithSnapshot), true, true);
1343         ui->treeView->setFocus();
1344     }
1345 }
1346
1347 void VoglEditor::on_prevDrawcallButton_clicked()
1348 {
1349     if (m_pApicallTreeModel != NULL)
1350     {
1351         vogleditor_apiCallTreeItem* pPrevItem = m_pApicallTreeModel->find_prev_drawcall(m_pCurrentCallTreeItem);
1352         selectApicallModelIndex(m_pApicallTreeModel->indexOf(pPrevItem), true, true);
1353         ui->treeView->setFocus();
1354     }
1355 }
1356
1357 void VoglEditor::on_nextDrawcallButton_clicked()
1358 {
1359     if (m_pApicallTreeModel != NULL)
1360     {
1361         vogleditor_apiCallTreeItem* pNextItem = m_pApicallTreeModel->find_next_drawcall(m_pCurrentCallTreeItem);
1362         selectApicallModelIndex(m_pApicallTreeModel->indexOf(pNextItem), true, true);
1363         ui->treeView->setFocus();
1364     }
1365 }
1366
1367
1368 void VoglEditor::on_program_edited(vogl_program_state* pNewProgramState)
1369 {
1370     VOGL_NOTE_UNUSED(pNewProgramState);
1371
1372     m_currentSnapshot->set_edited(true);
1373
1374     // update all the snapshot flags
1375     bool bFoundEditedSnapshot = false;
1376     recursive_update_snapshot_flags(m_pApicallTreeModel->root(), bFoundEditedSnapshot);
1377
1378     // 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,
1379     // 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
1380     // 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
1381     // API call instead of cycling through state options).
1382     ui->treeView->setFocus();
1383 }
1384
1385 // if an edited snapshot has already been found, mark the node (and all children) as dirty.
1386 void VoglEditor::recursive_update_snapshot_flags(vogleditor_apiCallTreeItem* pItem, bool& bFoundEditedSnapshot)
1387 {
1388     // check if this item has a snapshot shot
1389     if (pItem->has_snapshot())
1390     {
1391         if (!bFoundEditedSnapshot)
1392         {
1393             if (pItem->get_snapshot()->is_edited())
1394             {
1395                 bFoundEditedSnapshot = true;
1396             }
1397             else
1398             {
1399                 pItem->get_snapshot()->set_outdated(false);
1400             }
1401         }
1402         else
1403         {
1404             pItem->get_snapshot()->set_outdated(true);
1405         }
1406     }
1407
1408     for (int i = 0; i < pItem->childCount(); i++)
1409     {
1410         recursive_update_snapshot_flags(pItem->child(i), bFoundEditedSnapshot);
1411     }
1412 }
1413
1414 #undef VOGLEDITOR_DISABLE_TAB
1415 #undef VOGLEDITOR_ENABLE_TAB