1 /**************************************************************************
3 * Copyright 2013-2014 RAD Game Tools and Valve Software
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:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
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
24 **************************************************************************/
26 #include <QFileDialog>
27 #include <QHBoxLayout>
28 #include <QItemSelection>
30 #include <QSortFilterProxyModel>
31 #include <QSpacerItem>
32 #include <QToolButton>
33 #include <QMessageBox>
35 #include "ui_vogleditor.h"
36 #include "vogleditor.h"
38 #include "vogleditor_qapicalltreemodel.h"
39 #include "vogleditor_apicalltimelinemodel.h"
41 #include "vogl_platform.h"
42 #include "vogl_assert.h"
43 #include "vogl_file_utils.h"
44 #include "vogl_find_files.h"
46 #include "vogl_texture_format.h"
47 #include "vogl_trace_file_reader.h"
48 #include "vogl_trace_file_writer.h"
49 #include "vogleditor_qstatetreemodel.h"
50 #include "vogleditor_statetreetextureitem.h"
51 #include "vogleditor_statetreeprogramitem.h"
52 #include "vogleditor_statetreeshaderitem.h"
53 #include "vogleditor_statetreeframebufferitem.h"
54 #include "vogleditor_qtextureexplorer.h"
56 #define VOGLEDITOR_DISABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), false);
57 #define VOGLEDITOR_ENABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), true);
59 //----------------------------------------------------------------------------------------------------------------------
61 //----------------------------------------------------------------------------------------------------------------------
62 static void *g_actual_libgl_module_handle;
63 static QString g_PROJECT_NAME = "Vogl Editor";
65 //----------------------------------------------------------------------------------------------------------------------
66 // vogl_get_proc_address_helper
67 //----------------------------------------------------------------------------------------------------------------------
68 static vogl_void_func_ptr_t vogl_get_proc_address_helper(const char *pName)
72 vogl_void_func_ptr_t pFunc = g_actual_libgl_module_handle ? reinterpret_cast<vogl_void_func_ptr_t>(dlsym(g_actual_libgl_module_handle, pName)) : NULL;
74 if ((!pFunc) && (GL_ENTRYPOINT(glXGetProcAddress)))
75 pFunc = reinterpret_cast<vogl_void_func_ptr_t>( GL_ENTRYPOINT(glXGetProcAddress)(reinterpret_cast<const GLubyte*>(pName)) );
81 //----------------------------------------------------------------------------------------------------------------------
83 //----------------------------------------------------------------------------------------------------------------------
88 g_actual_libgl_module_handle = dlopen("libGL.so.1", RTLD_LAZY);
89 if (!g_actual_libgl_module_handle)
91 vogl_error_printf("%s: Failed loading libGL.so.1!\n", VOGL_FUNCTION_NAME);
95 GL_ENTRYPOINT(glXGetProcAddress) = reinterpret_cast<glXGetProcAddress_func_ptr_t>(dlsym(g_actual_libgl_module_handle, "glXGetProcAddress"));
96 if (!GL_ENTRYPOINT(glXGetProcAddress))
98 vogl_error_printf("%s: Failed getting address of glXGetProcAddress() from libGL.so.1!\n", VOGL_FUNCTION_NAME);
105 VoglEditor::VoglEditor(QWidget *parent) :
107 ui(new Ui::VoglEditor),
109 m_framebufferExplorer(NULL),
110 m_textureExplorer(NULL),
111 m_renderbufferExplorer(NULL),
112 m_programExplorer(NULL),
113 m_shaderExplorer(NULL),
115 m_currentSnapshot(NULL),
116 m_pCurrentCallTreeItem(NULL),
118 m_pPauseButton(NULL),
121 m_pTraceReader(NULL),
122 m_pTraceWriter(NULL),
123 m_pApicallTreeModel(NULL)
129 vogl_init_actual_gl_entrypoints(vogl_get_proc_address_helper);
132 m_statusLabel = new QLabel(ui->statusBar);
133 m_statusLabel->setBaseSize(150, 12);
134 ui->statusBar->addWidget(m_statusLabel, 1);
136 // setup framebuffer tab
137 QGridLayout* framebufferTab_layout = new QGridLayout;
138 m_framebufferExplorer = new vogleditor_QFramebufferExplorer(ui->framebufferTab);
139 framebufferTab_layout->addWidget(m_framebufferExplorer, 0, 0);
140 ui->framebufferTab->setLayout(framebufferTab_layout);
143 QGridLayout* textureTab_layout = new QGridLayout;
144 m_textureExplorer = new vogleditor_QTextureExplorer(ui->textureTab);
145 textureTab_layout->addWidget(m_textureExplorer, 0, 0);
146 ui->textureTab->setLayout(textureTab_layout);
148 // setup renderbuffer tab
149 QGridLayout* rbTab_layout = new QGridLayout;
150 m_renderbufferExplorer = new vogleditor_QTextureExplorer(ui->renderbufferTab);
151 rbTab_layout->addWidget(m_renderbufferExplorer, 0, 0);
152 ui->renderbufferTab->setLayout(rbTab_layout);
155 QGridLayout* programTab_layout = new QGridLayout;
156 m_programExplorer = new vogleditor_QProgramExplorer(ui->programTab);
157 programTab_layout->addWidget(m_programExplorer, 0, 0);
158 ui->programTab->setLayout(programTab_layout);
161 QGridLayout* shaderTab_layout = new QGridLayout;
162 m_shaderExplorer = new vogleditor_QShaderExplorer(ui->shaderTab);
163 shaderTab_layout->addWidget(m_shaderExplorer, 0, 0);
164 ui->shaderTab->setLayout(shaderTab_layout);
167 m_timeline = new vogleditor_QTimelineView();
168 m_timeline->setMinimumHeight(100);
169 ui->verticalLayout->addWidget(m_timeline);
170 ui->verticalLayout->removeWidget(ui->timelineViewPlaceholder);
172 // add buttons to toolbar
173 m_pPlayButton = new QToolButton(ui->mainToolBar);
174 m_pPlayButton->setText("Play trace");
175 m_pPlayButton->setEnabled(false);
177 m_pPauseButton = new QToolButton(ui->mainToolBar);
178 m_pPauseButton->setText("Pause");
179 m_pPauseButton->setEnabled(false);
181 m_pTrimButton = new QToolButton(ui->mainToolBar);
182 m_pTrimButton->setText("Trim");
183 m_pTrimButton->setEnabled(false);
185 m_pStopButton = new QToolButton(ui->mainToolBar);
186 m_pStopButton->setText("Stop");
187 m_pStopButton->setEnabled(false);
189 // Temporarily hide the other buttons (until asyncronous playback is supported)
190 m_pPauseButton->setVisible(false);
191 m_pTrimButton->setVisible(false);
192 m_pStopButton->setVisible(false);
194 ui->mainToolBar->addWidget(m_pPlayButton);
195 ui->mainToolBar->addWidget(m_pPauseButton);
196 ui->mainToolBar->addWidget(m_pTrimButton);
197 ui->mainToolBar->addWidget(m_pStopButton);
199 connect(m_pPlayButton, SIGNAL(clicked()), this, SLOT(playCurrentTraceFile()));
200 connect(m_pPauseButton, SIGNAL(clicked()), this, SLOT(pauseCurrentTraceFile()));
201 connect(m_pTrimButton, SIGNAL(clicked()), this, SLOT(trimCurrentTraceFile()));
202 connect(m_pStopButton, SIGNAL(clicked()), this, SLOT(stopCurrentTraceFile()));
204 connect(m_programExplorer, SIGNAL(program_edited(vogl_program_state*)), this, SLOT(on_program_edited(vogl_program_state*)));
206 reset_tracefile_ui();
209 VoglEditor::~VoglEditor()
214 if (m_textureExplorer != NULL)
216 delete m_textureExplorer;
217 m_textureExplorer = NULL;
220 if (m_renderbufferExplorer != NULL)
222 delete m_renderbufferExplorer;
223 m_renderbufferExplorer = NULL;
226 if (m_programExplorer != NULL)
228 delete m_programExplorer;
229 m_programExplorer = NULL;
232 if (m_shaderExplorer != NULL)
234 delete m_shaderExplorer;
235 m_shaderExplorer = NULL;
239 void VoglEditor::playCurrentTraceFile()
241 QCursor origCursor = cursor();
242 setCursor(Qt::WaitCursor);
245 m_pPlayButton->setEnabled(false);
246 m_pPauseButton->setEnabled(true);
247 m_pTrimButton->setEnabled(true);
248 m_pStopButton->setEnabled(true);
249 m_statusLabel->clear();
251 if (m_traceReplayer.replay(m_pTraceReader, m_pApicallTreeModel->root(), NULL, 0, true))
253 // replay was successful
254 m_pPlayButton->setEnabled(true);
255 m_pPauseButton->setEnabled(false);
256 m_pTrimButton->setEnabled(false);
257 m_pStopButton->setEnabled(false);
261 m_statusLabel->setText("Failed to replay the trace.");
264 setCursor(origCursor);
267 void VoglEditor::pauseCurrentTraceFile()
269 if (m_traceReplayer.pause())
272 m_pPlayButton->setEnabled(true);
273 m_pPauseButton->setEnabled(false);
274 m_pTrimButton->setEnabled(true);
275 m_pStopButton->setEnabled(true);
276 m_statusLabel->clear();
280 m_statusLabel->setText("Failed to pause the trace replay.");
284 void VoglEditor::trimCurrentTraceFile()
286 if (m_traceReplayer.trim())
288 m_statusLabel->clear();
292 m_statusLabel->setText("Failed to trim the trace replay.");
296 void VoglEditor::stopCurrentTraceFile()
298 if (m_traceReplayer.stop())
301 m_pPlayButton->setEnabled(true);
302 m_pPauseButton->setEnabled(false);
303 m_pTrimButton->setEnabled(false);
304 m_pStopButton->setEnabled(false);
305 m_statusLabel->clear();
309 m_statusLabel->setText("Failed to stop the trace replay.");
313 void VoglEditor::on_actionE_xit_triggered()
318 void VoglEditor::on_action_Open_triggered()
320 QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QString(),
321 tr("GLI Binary Files (*.bin);;JSON Files (*.json)"));
323 if (!fileName.isEmpty()) {
324 vogl::dynamic_string filename;
325 filename.set(fileName.toStdString().c_str());
327 if (open_trace_file(filename) == false) {
328 QMessageBox::critical(this, tr("Error"), tr("Could not open file"));
334 void VoglEditor::on_action_Close_triggered()
339 void VoglEditor::close_trace_file()
341 if (m_pTraceReader != NULL)
343 m_pTraceReader->close();
344 vogl_delete(m_pTraceReader);
345 m_pTraceReader = NULL;
347 if (m_pTraceWriter != NULL)
349 m_pTraceWriter->close();
350 vogl_delete(m_pTraceWriter);
351 m_pTraceWriter = NULL;
354 setWindowTitle(g_PROJECT_NAME);
356 m_openFilename.clear();
357 m_backtraceToJsonMap.clear();
358 m_backtraceDoc.clear();
359 m_searchApicallResults.clear();
361 reset_tracefile_ui();
363 ui->treeView->setModel(NULL);
364 ui->machineInfoText->clear();
365 ui->backtraceText->clear();
366 m_timeline->setModel(NULL);
367 m_timeline->repaint();
369 if (m_pTimelineModel != NULL)
371 delete m_pTimelineModel;
372 m_pTimelineModel = NULL;
377 void VoglEditor::write_child_api_calls(vogleditor_apiCallTreeItem* pItem, FILE* pFile)
379 QString string = pItem->columnData(VOGL_ACTC_APICALL, Qt::DisplayRole).toString();
380 vogl_fwrite(string.toStdString().c_str(), 1, string.size(), pFile);
381 vogl_fwrite("\r\n", 1, 2, pFile);
383 for (int i = 0; i < pItem->childCount(); i++)
385 write_child_api_calls(pItem->child(i), pFile);
389 void VoglEditor::on_actionExport_API_Calls_triggered()
391 QString suggestedName = m_openFilename;
393 int lastIndex = suggestedName.lastIndexOf('-');
396 suggestedName = suggestedName.remove(lastIndex, suggestedName.size() - lastIndex);
398 suggestedName += "-ApiCalls.txt";
400 QString fileName = QFileDialog::getSaveFileName(this, tr("Export API Calls"), suggestedName, tr("Text (*.txt)"));
402 if (!fileName.isEmpty())
404 vogl::dynamic_string filename;
405 filename.set(fileName.toStdString().c_str());
407 FILE* pFile = vogl_fopen(filename.c_str(), "w");
408 vogleditor_QApiCallTreeModel* pModel = static_cast<vogleditor_QApiCallTreeModel*>(ui->treeView->model());
409 vogleditor_apiCallTreeItem* pRoot = pModel->root();
410 for (int i = 0; i < pRoot->childCount(); i++)
412 write_child_api_calls(pRoot->child(i), pFile);
418 static const unsigned int VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1 = 1;
419 static const unsigned int VOGLEDITOR_SESSION_FILE_FORMAT_VERSION = VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1;
421 bool VoglEditor::load_session_from_disk(QString sessionFile)
424 json_document sessionDoc;
425 if (!sessionDoc.deserialize_file(sessionFile.toStdString().c_str()))
430 // look for expected metadata
431 json_node* pMetadata = sessionDoc.get_root()->find_child_object("metadata");
432 if (pMetadata == NULL)
437 const json_value& rFormatVersion = pMetadata->find_value("session_file_format_version");
438 if (!rFormatVersion.is_valid())
443 if (rFormatVersion.as_uint32() != VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1)
448 // load base trace file
449 json_node* pBaseTraceFile = sessionDoc.get_root()->find_child_object("base_trace_file");
450 if (pBaseTraceFile == NULL)
455 const json_value& rBaseTraceFilePath = pBaseTraceFile->find_value("rel_path");
456 const json_value& rBaseTraceFileUuid = pBaseTraceFile->find_value("uuid");
458 if (!rBaseTraceFilePath.is_valid() || !rBaseTraceFileUuid.is_valid())
463 dynamic_string sessionPathName;
464 dynamic_string sessionFileName;
465 file_utils::split_path(sessionFile.toStdString().c_str(), sessionPathName, sessionFileName);
467 dynamic_string traceFilePath = sessionPathName;
468 traceFilePath.append(rBaseTraceFilePath.as_string());
470 if (!open_trace_file(traceFilePath))
475 // TODO: verify UUID of the loaded trace file
477 // load session data if it is available
478 json_node* pSessionData = sessionDoc.get_root()->find_child_object("session_data");
479 if (pSessionData != NULL)
481 const json_value& rSessionPath = pSessionData->find_value("rel_path");
482 if (!rSessionPath.is_valid())
487 dynamic_string sessionDataPath = sessionPathName;
488 sessionDataPath.append(rSessionPath.as_string());
490 vogl_loose_file_blob_manager file_blob_manager;
491 file_blob_manager.init(cBMFReadWrite, sessionDataPath.c_str());
492 vogl_blob_manager* pBlob_manager = static_cast<vogl_blob_manager*>(&file_blob_manager);
495 const json_node* pSnapshots = pSessionData->find_child_array("snapshots");
496 for (unsigned int i = 0; i < pSnapshots->size(); i++)
498 const json_node* pSnapshotNode = pSnapshots->get_value_as_object(i);
500 const json_value& uuid = pSnapshotNode->find_value("uuid");
501 const json_value& isValid = pSnapshotNode->find_value("is_valid");
502 const json_value& isEdited = pSnapshotNode->find_value("is_edited");
503 const json_value& isOutdated = pSnapshotNode->find_value("is_outdated");
504 const json_value& frameNumber = pSnapshotNode->find_value("frame_number");
505 const json_value& callIndex = pSnapshotNode->find_value("call_index");
506 const json_value& path = pSnapshotNode->find_value("rel_path");
508 // make sure expected nodes are valid
509 if (!isValid.is_valid() || !isEdited.is_valid() || !isOutdated.is_valid())
514 vogl_gl_state_snapshot* pSnapshot = NULL;
516 if (path.is_valid() && isValid.as_bool() && uuid.is_valid())
518 dynamic_string snapshotPath = sessionDataPath;
519 snapshotPath.append(path.as_string());
522 json_document snapshotDoc;
523 if (!snapshotDoc.deserialize_file(snapshotPath.c_str()))
528 // attempt to verify the snapshot file
529 json_node* pSnapshotRoot = snapshotDoc.get_root();
530 if (pSnapshotRoot == NULL)
532 vogl_warning_printf("Invalid snapshot file at %s.", path.as_string_ptr());
536 const json_value& snapshotUuid = pSnapshotRoot->find_value("uuid");
537 if (!snapshotUuid.is_valid())
539 vogl_warning_printf("Invalid 'uuid' in snapshot file at %s.", path.as_string_ptr());
543 if (snapshotUuid.as_string() != uuid.as_string())
545 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());
549 vogl_ctypes trace_gl_ctypes(m_pTraceReader->get_sof_packet().m_pointer_sizes);
550 pSnapshot = vogl_new(vogl_gl_state_snapshot);
551 if (!pSnapshot->deserialize(*snapshotDoc.get_root(), *pBlob_manager, &trace_gl_ctypes))
553 vogl_delete(pSnapshot);
555 vogl_warning_printf("Unable to deserialize the snapshot with uuid %s.", uuid.as_string_ptr());
560 vogleditor_gl_state_snapshot* pContainer = vogl_new(vogleditor_gl_state_snapshot, pSnapshot);
561 pContainer->set_edited(isEdited.as_bool());
562 pContainer->set_outdated(isOutdated.as_bool());
564 if (callIndex.is_valid())
566 // the snapshot is associated with an api call
567 vogleditor_apiCallTreeItem* pItem = m_pApicallTreeModel->find_call_number(callIndex.as_uint64());
570 pItem->set_snapshot(pContainer);
574 vogl_warning_printf("Unable to find API call index %" PRIu64 " to load the snapshot into.", callIndex.as_uint64());
575 if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
576 if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
579 else if (frameNumber.is_valid())
581 // the snapshot is associated with a frame.
582 // frame snapshots have the additional requirement that the snapshot itself MUST exist since
583 // we only save a frame snapshot if it is the inital frame and it has been edited.
584 // If we allow NULL snapshots, that we could accidently remove the initial snapshot that was loaded with the trace file.
585 if (pSnapshot != NULL)
587 vogleditor_apiCallTreeItem* pItem = m_pApicallTreeModel->find_frame_number(frameNumber.as_uint64());
590 pItem->set_snapshot(pContainer);
594 vogl_warning_printf("Unable to find frame number %" PRIu64 " to load the snapshot into.", frameNumber.as_uint64());
595 if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
596 if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
602 vogl_warning_printf("Session file contains invalid call or frame number for snapshot with uuid %s", uuid.as_string_ptr());
603 if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
604 if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
613 * 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.
614 * Note that not all of this information is currently supported (either by VoglEditor or the save/load functionality).
616 * sample data structure for version 1:
619 "session_file_format_version" : "0x1" <- would need to be updated when organization of existing data is changed
621 "base_trace_file" : {
622 "path" : "../traces/trimmed4.bin",
623 "uuid" : [ 2761638124, 1361789091, 2623121922, 1789156619 ]
626 "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/",
629 "uuid" : "B346B680801ED2F5144E421DEA5EFDCC",
632 "is_outdated" : false,
636 "uuid" : "BC261B884088DBEADF376A03A489F2B9",
639 "is_outdated" : false,
640 "call_index" : 881069,
641 "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/snapshot_call_881069.json"
644 "uuid" : "176DE3DEAA437B871FE122C84D5432E3",
647 "is_outdated" : false,
648 "call_index" : 881075,
649 "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/snapshot_call_881075.json"
654 "is_outdated" : true,
655 "call_index" : 881080
661 bool VoglEditor::save_session_to_disk(QString sessionFile)
663 dynamic_string sessionPathName;
664 dynamic_string sessionFileName;
665 file_utils::split_path(sessionFile.toStdString().c_str(), sessionPathName, sessionFileName);
667 // modify the session file name to make a sessiondata folder
668 QString sessionDataFolder(sessionFileName.c_str());
669 int lastIndex = sessionDataFolder.lastIndexOf('.');
672 sessionDataFolder = sessionDataFolder.remove(lastIndex, sessionDataFolder.size() - lastIndex);
674 sessionDataFolder += "-sessiondata/";
676 dynamic_string sessionDataPath = sessionPathName;
677 sessionDataPath.append(sessionDataFolder.toStdString().c_str());
678 file_utils::create_directories(sessionDataPath, false);
680 vogl_loose_file_blob_manager file_blob_manager;
681 file_blob_manager.init(cBMFReadWrite, sessionDataPath.c_str());
682 vogl_blob_manager* pBlob_manager = static_cast<vogl_blob_manager*>(&file_blob_manager);
684 QCursor origCursor = this->cursor();
685 setCursor(Qt::WaitCursor);
687 json_document sessionDoc;
688 json_node& metadata = sessionDoc.get_root()->add_object("metadata");
689 metadata.add_key_value("session_file_format_version", to_hex_string(VOGLEDITOR_SESSION_FILE_FORMAT_VERSION));
691 // find relative path from session file to trace file
693 QString absoluteTracePath = relativeAppDir.absoluteFilePath(m_openFilename.toStdString().c_str());
694 QDir absoluteSessionFileDir(sessionPathName.c_str());
695 QString tracePathRelativeToSessionFile = absoluteSessionFileDir.relativeFilePath(absoluteTracePath);
697 json_node& baseTraceFile = sessionDoc.get_root()->add_object("base_trace_file");
698 baseTraceFile.add_key_value("rel_path", tracePathRelativeToSessionFile.toStdString().c_str());
699 json_node &uuid_array = baseTraceFile.add_array("uuid");
700 for (uint i = 0; i < VOGL_ARRAY_SIZE(m_pTraceReader->get_sof_packet().m_uuid); i++)
702 uuid_array.add_value(m_pTraceReader->get_sof_packet().m_uuid[i]);
705 json_node& sessionDataNode = sessionDoc.get_root()->add_object("session_data");
706 sessionDataNode.add_key_value("rel_path", sessionDataFolder.toStdString().c_str());
707 json_node& snapshotArray = sessionDataNode.add_array("snapshots");
709 vogleditor_apiCallTreeItem* pItem = m_pApicallTreeModel->find_next_snapshot(NULL);
710 vogleditor_apiCallTreeItem* pLastItem = NULL;
711 bool bSavedSuccessfully = true;
712 while (pItem != pLastItem && pItem != NULL)
714 dynamic_string filename;
716 json_node& snapshotNode = snapshotArray.add_object();
717 if (pItem->get_snapshot()->get_snapshot() != NULL)
719 dynamic_string strUUID;
720 snapshotNode.add_key_value("uuid", pItem->get_snapshot()->get_snapshot()->get_uuid().get_string(strUUID));
722 snapshotNode.add_key_value("is_valid", pItem->get_snapshot()->is_valid());
723 snapshotNode.add_key_value("is_edited", pItem->get_snapshot()->is_edited());
724 snapshotNode.add_key_value("is_outdated", pItem->get_snapshot()->is_outdated());
726 if (pItem->apiCallItem() != NULL)
728 uint64_t callIndex = pItem->apiCallItem()->globalCallIndex();
729 snapshotNode.add_key_value("call_index", callIndex);
730 if (pItem->get_snapshot()->get_snapshot() != NULL)
732 filename = filename.format("snapshot_call_%" PRIu64 ".json", callIndex);
733 snapshotNode.add_key_value("rel_path", filename);
734 dynamic_string filepath = sessionDataPath;
735 filepath.append(filename);
736 if (!save_snapshot_to_disk(pItem->get_snapshot()->get_snapshot(), filepath, pBlob_manager))
738 bSavedSuccessfully = false;
743 else if (pItem->frameItem() != NULL)
745 // the first frame of a trim will have a snapshot.
746 // this should only be saved out if the snapshot has been edited
747 uint64_t frameNumber = pItem->frameItem()->frameNumber();
748 snapshotNode.add_key_value("frame_number", frameNumber);
749 if (pItem->get_snapshot()->is_edited())
751 filename = filename.format("snapshot_frame_%" PRIu64 ".json", frameNumber);
752 snapshotNode.add_key_value("rel_path", filename);
753 dynamic_string filepath = sessionDataPath;
754 filepath.append(filename);
755 if (!save_snapshot_to_disk(pItem->get_snapshot()->get_snapshot(), filepath, pBlob_manager))
757 bSavedSuccessfully = false;
764 pItem = m_pApicallTreeModel->find_next_snapshot(pLastItem);
767 if (bSavedSuccessfully)
769 bSavedSuccessfully = sessionDoc.serialize_to_file(sessionFile.toStdString().c_str());
772 setCursor(origCursor);
774 return bSavedSuccessfully;
777 bool VoglEditor::save_snapshot_to_disk(vogl_gl_state_snapshot *pSnapshot, dynamic_string filename, vogl_blob_manager *pBlob_manager)
779 if (pSnapshot == NULL)
786 vogl_ctypes trace_gl_ctypes(m_pTraceReader->get_sof_packet().m_pointer_sizes);
788 if (!pSnapshot->serialize(*doc.get_root(), *pBlob_manager, &trace_gl_ctypes))
790 vogl_error_printf("Failed serializing state snapshot document!\n");
793 else if (!doc.serialize_to_file(filename.get_ptr(), true))
795 vogl_error_printf("Failed writing state snapshot to file \"%s\"!\n", filename.get_ptr());
800 vogl_printf("Successfully wrote JSON snapshot to file \"%s\"\n", filename.get_ptr());
806 //----------------------------------------------------------------------------------------------------------------------
807 // read_state_snapshot_from_trace
808 //----------------------------------------------------------------------------------------------------------------------
809 vogl_gl_state_snapshot* VoglEditor::read_state_snapshot_from_trace(vogl_trace_file_reader* pTrace_reader)
811 vogl_ctypes trace_gl_ctypes(pTrace_reader->get_sof_packet().m_pointer_sizes);
813 vogl_trace_packet keyframe_trace_packet(&trace_gl_ctypes);
815 pTrace_reader->seek_to_frame(0);
817 vogl_gl_state_snapshot *pSnapshot = NULL;
818 bool found_snapshot = false;
821 vogl_trace_file_reader::trace_file_reader_status_t read_status = pTrace_reader->read_next_packet();
823 if ((read_status != vogl_trace_file_reader::cOK) && (read_status != vogl_trace_file_reader::cEOF))
825 vogl_error_printf("%s: Failed reading from keyframe trace file!\n", VOGL_FUNCTION_NAME);
829 if ((read_status == vogl_trace_file_reader::cEOF) || (pTrace_reader->get_packet_type() == cTSPTEOF))
831 vogl_error_printf("%s: Failed finding state snapshot in keyframe file!\n", VOGL_FUNCTION_NAME);
835 if (pTrace_reader->get_packet_type() != cTSPTGLEntrypoint)
838 if (!keyframe_trace_packet.deserialize(pTrace_reader->get_packet_buf().get_ptr(), pTrace_reader->get_packet_buf().size(), false))
840 vogl_error_printf("%s: Failed parsing GL entrypoint packet in keyframe file\n", VOGL_FUNCTION_NAME);
844 const vogl_trace_gl_entrypoint_packet *pGL_packet = &pTrace_reader->get_packet<vogl_trace_gl_entrypoint_packet>();
845 gl_entrypoint_id_t entrypoint_id = static_cast<gl_entrypoint_id_t>(pGL_packet->m_entrypoint_id);
847 if (vogl_is_swap_buffers_entrypoint(entrypoint_id) || vogl_is_draw_entrypoint(entrypoint_id) || vogl_is_make_current_entrypoint(entrypoint_id))
849 vogl_error_printf("Failed finding state snapshot in keyframe file!\n");
853 switch (entrypoint_id)
855 case VOGL_ENTRYPOINT_glInternalTraceCommandRAD:
857 GLuint cmd = keyframe_trace_packet.get_param_value<GLuint>(0);
858 GLuint size = keyframe_trace_packet.get_param_value<GLuint>(1); VOGL_NOTE_UNUSED(size);
860 if (cmd == cITCRKeyValueMap)
862 key_value_map &kvm = keyframe_trace_packet.get_key_value_map();
864 dynamic_string cmd_type(kvm.get_string("command_type"));
865 if (cmd_type == "state_snapshot")
867 dynamic_string id(kvm.get_string("binary_id"));
870 vogl_error_printf("%s: Missing binary_id field in glInternalTraceCommandRAD key_valye_map command type: \"%s\"\n", VOGL_FUNCTION_NAME, cmd_type.get_ptr());
874 uint8_vec snapshot_data;
876 timed_scope ts("get_multi_blob_manager().get");
877 if (!pTrace_reader->get_multi_blob_manager().get(id, snapshot_data) || (snapshot_data.is_empty()))
879 vogl_error_printf("%s: Failed reading snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
884 vogl_message_printf("%s: Deserializing state snapshot \"%s\", %u bytes\n", VOGL_FUNCTION_NAME, id.get_ptr(), snapshot_data.size());
888 timed_scope ts("doc.binary_deserialize");
889 if (!doc.binary_deserialize(snapshot_data) || (!doc.get_root()))
891 vogl_error_printf("%s: Failed deserializing JSON snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
896 pSnapshot = vogl_new(vogl_gl_state_snapshot);
898 timed_scope ts("pSnapshot->deserialize");
899 if (!pSnapshot->deserialize(*doc.get_root(), pTrace_reader->get_multi_blob_manager(), &trace_gl_ctypes))
901 vogl_delete(pSnapshot);
904 vogl_error_printf("%s: Failed deserializing snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
908 found_snapshot = true;
917 } while (!found_snapshot);
923 bool VoglEditor::open_trace_file(dynamic_string filename)
925 QCursor origCursor = this->cursor();
926 this->setCursor(Qt::WaitCursor);
928 vogl_loose_file_blob_manager file_blob_manager;
929 dynamic_string keyframe_trace_path(file_utils::get_pathname(filename.get_ptr()));
930 file_blob_manager.init(cBMFReadable, keyframe_trace_path.get_ptr());
932 dynamic_string actual_keyframe_filename;
934 vogl_trace_file_reader* tmpReader = vogl_open_trace_file(filename, actual_keyframe_filename, NULL);
936 if (tmpReader == NULL)
938 m_statusLabel->setText("Failed to open: ");
939 m_statusLabel->setText(m_statusLabel->text().append(filename.c_str()));
940 this->setCursor(origCursor);
945 m_statusLabel->clear();
948 // now that we know the new trace file can be opened,
949 // close the old one, and update the trace reader
951 m_pTraceReader = tmpReader;
953 vogl_ctypes trace_ctypes;
954 trace_ctypes.init(m_pTraceReader->get_sof_packet().m_pointer_sizes);
955 m_pTraceWriter = vogl_new(vogl_trace_file_writer, &trace_ctypes);
957 dynamic_string traceSessionFilename = "vogleditor_session.bin";
958 m_pTraceWriter->open(traceSessionFilename.c_str());
960 m_pApicallTreeModel = new vogleditor_QApiCallTreeModel(m_pTraceReader);
961 ui->treeView->setModel(m_pApicallTreeModel);
963 if (ui->treeView->selectionModel() != NULL)
965 //connect(ui->treeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), this, SLOT(on_treeView_selectionChanged(const QItemSelection&, const QItemSelection&)));
966 connect(ui->treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(on_treeView_currentChanged(const QModelIndex &, const QModelIndex &)));
969 if (m_pApicallTreeModel->hasChildren())
971 ui->treeView->setExpanded(m_pApicallTreeModel->index(0,0), true);
972 ui->treeView->setCurrentIndex(m_pApicallTreeModel->index(0,0));
975 int flagsColumnWidth = 30;
976 ui->treeView->header()->setMinimumSectionSize(flagsColumnWidth);
977 ui->treeView->header()->moveSection(VOGL_ACTC_FLAGS, 0);
978 ui->treeView->setColumnWidth(VOGL_ACTC_FLAGS, flagsColumnWidth);
980 int width = ui->treeView->width() - flagsColumnWidth - 30; // subtract a little extra for the scrollbar width
981 ui->treeView->setColumnWidth(VOGL_ACTC_APICALL, width * 0.7);
982 ui->treeView->setColumnWidth(VOGL_ACTC_INDEX, width * 0.15);
983 ui->treeView->setColumnWidth(VOGL_ACTC_DURATION, width * 0.15);
985 ui->searchTextBox->setEnabled(true);
986 ui->searchPrevButton->setEnabled(true);
987 ui->searchNextButton->setEnabled(true);
989 ui->action_Close->setEnabled(true);
990 ui->actionSave_Session->setEnabled(true);
991 ui->actionExport_API_Calls->setEnabled(true);
993 ui->prevSnapshotButton->setEnabled(true);
994 ui->nextSnapshotButton->setEnabled(true);
995 ui->prevDrawcallButton->setEnabled(true);
996 ui->nextDrawcallButton->setEnabled(true);
998 m_backtraceToJsonMap.clear();
999 m_backtraceDoc.clear();
1001 // Extract backtrace map and machine info from trace archive
1002 if (m_pTraceReader->get_archive_blob_manager().is_initialized())
1005 uint8_vec backtrace_data;
1006 bool bBacktraceVisible = false;
1007 if (m_pTraceReader->get_archive_blob_manager().does_exist(VOGL_TRACE_ARCHIVE_BACKTRACE_MAP_ADDRS_FILENAME))
1009 //$ TODO mikesart: read MAP_SYMS data here when symbols have been resolved.
1010 if (m_pTraceReader->get_archive_blob_manager().get(VOGL_TRACE_ARCHIVE_BACKTRACE_MAP_ADDRS_FILENAME, backtrace_data))
1012 json_node* pRoot = m_backtraceDoc.get_root();
1013 if (m_backtraceDoc.deserialize((const char*)backtrace_data.get_ptr(), backtrace_data.size()))
1015 bBacktraceVisible = pRoot->size() > 0;
1016 for (uint i = 0; i < pRoot->size(); i++)
1018 json_node* pChild = pRoot->get_child(i);
1020 VOGL_ASSERT("Backtrace node does not have an 'index' child" && pChild != NULL && pChild->get_value_as_uint32("index", index));
1021 if (pChild != NULL && pChild->get_value_as_uint32("index", index))
1023 m_backtraceToJsonMap.insert(index, pChild);
1030 QList<int> backtraceSplitterSizes = ui->splitter_3->sizes();
1031 int backtraceSplitterTotalSize = backtraceSplitterSizes[0] + backtraceSplitterSizes[1];
1032 QList<int> newBacktraceSplitterSizes;
1033 if (!bBacktraceVisible)
1035 newBacktraceSplitterSizes.append(backtraceSplitterTotalSize);
1036 newBacktraceSplitterSizes.append(0);
1037 ui->splitter_3->setSizes(newBacktraceSplitterSizes);
1041 newBacktraceSplitterSizes << (backtraceSplitterTotalSize * 0.75)
1042 << (backtraceSplitterTotalSize * 0.25);
1043 ui->splitter_3->setSizes(newBacktraceSplitterSizes);
1047 displayMachineInfo();
1050 m_openFilename = filename.c_str();
1052 setWindowTitle(m_openFilename + " - " + g_PROJECT_NAME);
1054 ui->tabWidget->setCurrentWidget(ui->framebufferTab);
1057 m_pPlayButton->setEnabled(true);
1058 m_pPauseButton->setEnabled(false);
1059 m_pTrimButton->setEnabled(false);
1060 m_pStopButton->setEnabled(false);
1063 m_pTimelineModel = new vogleditor_apiCallTimelineModel(m_pApicallTreeModel->root());
1064 m_timeline->setModel(m_pTimelineModel);
1065 m_timeline->repaint();
1067 this->setCursor(origCursor);
1071 void VoglEditor::displayMachineInfoHelper(QString prefix, const QString& sectionKeyStr, const vogl::json_value& value, QString& rMachineInfoStr)
1073 if (value.is_array())
1075 const json_node* pNode = value.get_node_ptr();
1076 for (uint element = 0; element < pNode->size(); element++)
1078 dynamic_string elementStr = pNode->get_value(element).as_string();
1080 elementStr = elementStr.replace("\n", "\n\t");
1082 rMachineInfoStr += "\t";
1083 rMachineInfoStr += elementStr.get_ptr();
1084 rMachineInfoStr += "\n";
1087 rMachineInfoStr += "\n";
1089 else if (value.is_node())
1091 // Check if this is the modoule list.
1092 bool is_module_list = (sectionKeyStr == "module_list");
1093 const json_node* pNode = value.get_node_ptr();
1095 for (uint i = 0; i < pNode->size(); i++)
1097 dynamic_string key = pNode->get_key(i);
1098 const json_value &value2 = pNode->get_value(i);
1100 rMachineInfoStr += prefix;
1101 // If it's the module list, then the key is the filename and we want to display that last.
1102 if (!is_module_list)
1103 rMachineInfoStr += key.c_str();
1105 if (value2.is_array())
1107 const json_node* pNode2 = value2.get_node_ptr();
1109 // If this it module_list, then we get these items: base address, address size, uuid
1110 // Check in btrace_get_machine_info() to see what's written there.
1111 for (uint element = 0; element < pNode2->size(); element++)
1113 const json_value &json_val = pNode2->get_value(element);
1115 if (json_val.is_string())
1117 dynamic_string str = pNode2->get_value(element).as_string();
1118 rMachineInfoStr += str.c_str();
1123 buf.format("%" PRIx64, json_val.as_uint64());
1124 rMachineInfoStr += buf.c_str();
1127 rMachineInfoStr += "\t";
1132 rMachineInfoStr += ": ";
1133 rMachineInfoStr += value2.as_string_ptr();
1136 // Display the filename if this is the module_list.
1138 rMachineInfoStr += key.c_str();
1139 rMachineInfoStr += "\n";
1142 rMachineInfoStr += "\n";
1144 else if (value.is_string())
1146 rMachineInfoStr += value.as_string_ptr();
1150 rMachineInfoStr += value.as_string_ptr();
1154 void VoglEditor::displayMachineInfo()
1156 VOGL_ASSERT(m_pTraceReader != NULL);
1158 bool bMachineInfoVisible = false;
1159 if (m_pTraceReader->get_archive_blob_manager().does_exist(VOGL_TRACE_ARCHIVE_MACHINE_INFO_FILENAME))
1161 uint8_vec machine_info_data;
1162 if (m_pTraceReader->get_archive_blob_manager().get(VOGL_TRACE_ARCHIVE_MACHINE_INFO_FILENAME, machine_info_data))
1164 bMachineInfoVisible = true;
1166 json_node *pRoot = doc.get_root();
1167 if (doc.deserialize((const char*)machine_info_data.get_ptr(), machine_info_data.size()))
1170 for (uint i = 0; i < pRoot->size(); i++)
1172 dynamic_string sectionKeyStr = pRoot->get_key(i);
1173 text += pRoot->get_key(i).c_str();
1176 QString keyStr = sectionKeyStr.c_str();
1177 displayMachineInfoHelper("\t", keyStr, pRoot->get_value(i), text);
1180 ui->machineInfoText->setText(text);
1185 if (bMachineInfoVisible)
1187 if (ui->tabWidget->indexOf(ui->machineInfoTab) == -1)
1190 ui->tabWidget->insertTab(0, ui->machineInfoTab, "Machine Info");
1194 VOGLEDITOR_ENABLE_TAB(ui->machineInfoTab);
1199 ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->machineInfoTab));
1203 void VoglEditor::reset_tracefile_ui()
1205 ui->action_Close->setEnabled(false);
1206 ui->actionExport_API_Calls->setEnabled(false);
1207 ui->actionSave_Session->setEnabled(false);
1209 ui->prevSnapshotButton->setEnabled(false);
1210 ui->nextSnapshotButton->setEnabled(false);
1211 ui->prevDrawcallButton->setEnabled(false);
1212 ui->nextDrawcallButton->setEnabled(false);
1213 ui->searchTextBox->clear();
1214 ui->searchTextBox->setEnabled(false);
1215 ui->searchPrevButton->setEnabled(false);
1216 ui->searchNextButton->setEnabled(false);
1218 m_statusLabel->clear();
1219 m_pPlayButton->setEnabled(false);
1220 m_pPauseButton->setEnabled(false);
1221 m_pTrimButton->setEnabled(false);
1222 m_pStopButton->setEnabled(false);
1224 VOGLEDITOR_DISABLE_TAB(ui->machineInfoTab);
1226 reset_snapshot_ui();
1229 void VoglEditor::reset_snapshot_ui()
1231 m_currentSnapshot = NULL;
1233 m_framebufferExplorer->clear();
1234 m_textureExplorer->clear();
1235 m_renderbufferExplorer->clear();
1236 m_programExplorer->clear();
1237 m_shaderExplorer->clear();
1239 ui->stateTreeView->setModel(NULL);
1241 QWidget* pCurrentTab = ui->tabWidget->currentWidget();
1243 VOGLEDITOR_DISABLE_TAB(ui->stateTab);
1244 VOGLEDITOR_DISABLE_TAB(ui->framebufferTab);
1245 VOGLEDITOR_DISABLE_TAB(ui->programTab);
1246 VOGLEDITOR_DISABLE_TAB(ui->shaderTab);
1247 VOGLEDITOR_DISABLE_TAB(ui->textureTab);
1248 VOGLEDITOR_DISABLE_TAB(ui->renderbufferTab);
1250 ui->tabWidget->setCurrentWidget(pCurrentTab);
1253 /// 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
1254 /// and also has no children. The pMostRecentSnapshot parameter will be updated to point to the desired snapshot.
1255 /// This function does not follow a traditional DFS search because we need to find the desired snapshot then return the one before it.
1256 /// An alternative approach would be to keep a stack of the found snapshots, or even to build up that stack / list as the user
1257 /// generates new snapshots.
1258 vogleditor_gl_state_snapshot* VoglEditor::findMostRecentSnapshot_helper(vogleditor_apiCallTreeItem* pItem, vogleditor_gl_state_snapshot*& pMostRecentSnapshot, const vogleditor_gl_state_snapshot* pCurSnapshot)
1260 // check if this item has a snapshot shot
1261 if (pItem->has_snapshot())
1263 vogleditor_gl_state_snapshot* pTmp = pItem->get_snapshot();
1264 if (pTmp == pCurSnapshot)
1266 // if we've reached the item with the current snapshot, we want to return the previous snapshot.
1271 // update most recent snapshot
1272 pMostRecentSnapshot = pTmp;
1276 for (int i = 0; i < pItem->childCount(); i++)
1278 vogleditor_gl_state_snapshot* pTmp = findMostRecentSnapshot_helper(pItem->child(i), pMostRecentSnapshot, pCurSnapshot);
1281 if (pTmp == pCurSnapshot)
1283 // if we've reached the item with the current snapshot, we want to return the previous snapshot.
1288 // update most recent snapshot
1289 pMostRecentSnapshot = pTmp;
1297 /// This function exists just to simplify the interaction with the helper, so that there no confusion between
1298 /// whether the returned value, or passed in reference parameter should be used as the most recent snapshot.
1299 /// It will either return NULL if there is no recent snapshot (which should only happen for the very first snapshot
1300 /// in a trace), or a pointer to a valid snapshot.
1301 vogleditor_gl_state_snapshot* VoglEditor::findMostRecentSnapshot(vogleditor_apiCallTreeItem* pItem, const vogleditor_gl_state_snapshot* pCurSnapshot)
1303 vogleditor_gl_state_snapshot* pMostRecentSnapshot = NULL;
1304 findMostRecentSnapshot_helper(pItem, pMostRecentSnapshot, pCurSnapshot);
1305 return pMostRecentSnapshot;
1308 void VoglEditor::update_ui_for_snapshot(vogleditor_gl_state_snapshot* pStateSnapshot)
1310 if (pStateSnapshot == NULL)
1312 reset_snapshot_ui();
1316 if (pStateSnapshot->is_valid() == false)
1318 reset_snapshot_ui();
1322 if (m_currentSnapshot == pStateSnapshot)
1324 // no need to update if it is the same snapshot
1328 m_currentSnapshot = pStateSnapshot;
1330 if (ui->stateTreeView->model() != NULL)
1332 if (static_cast<vogleditor_QStateTreeModel*>(ui->stateTreeView->model())->get_snapshot() == m_currentSnapshot)
1334 // displaying the same snapshot, return
1339 QCursor origCursor = this->cursor();
1340 this->setCursor(Qt::WaitCursor);
1343 vogleditor_QStateTreeModel* pStateModel = new vogleditor_QStateTreeModel(NULL);
1345 vogleditor_QApiCallTreeModel* pTreeModel = static_cast<vogleditor_QApiCallTreeModel*>(ui->treeView->model());
1346 vogleditor_gl_state_snapshot* pBaseSnapshot = findMostRecentSnapshot(pTreeModel->root(), m_currentSnapshot);
1347 pStateModel->set_diff_base_snapshot(pBaseSnapshot);
1349 pStateModel->set_snapshot(pStateSnapshot);
1351 ui->stateTreeView->setModel(pStateModel);
1352 ui->stateTreeView->expandToDepth(1);
1353 ui->stateTreeView->setColumnWidth(0, ui->stateTreeView->width() * 0.5);
1355 VOGLEDITOR_ENABLE_TAB(ui->stateTab);
1357 if (pStateSnapshot->get_contexts().size() > 0)
1359 vogl_trace_ptr_value curContextHandle = pStateSnapshot->get_cur_trace_context();
1360 if (curContextHandle != 0)
1362 vogl_context_snapshot* pContext = pStateSnapshot->get_context(curContextHandle);
1365 vogl_gl_object_state_ptr_vec textureObjects;
1366 pContext->get_all_objects_of_category(cGLSTTexture, textureObjects);
1367 m_textureExplorer->set_texture_objects(textureObjects);
1369 GLuint curActiveTextureUnit = pContext->get_general_state().get_value<GLuint>(GL_ACTIVE_TEXTURE);
1370 if (curActiveTextureUnit >= GL_TEXTURE0 && curActiveTextureUnit < (GL_TEXTURE0 + pContext->get_context_info().get_max_texture_image_units()))
1372 GLuint cur2DBinding = pContext->get_general_state().get_value<GLuint>(GL_TEXTURE_2D_BINDING_EXT, curActiveTextureUnit - GL_TEXTURE0);
1373 displayTexture(cur2DBinding, false);
1377 vogl_gl_object_state_ptr_vec renderbufferObjects;
1378 pContext->get_all_objects_of_category(cGLSTRenderbuffer, renderbufferObjects);
1379 m_renderbufferExplorer->set_texture_objects(renderbufferObjects);
1380 if (renderbufferObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->renderbufferTab); }
1383 vogl_gl_object_state_ptr_vec framebufferObjects;
1384 pContext->get_all_objects_of_category(cGLSTFramebuffer, framebufferObjects);
1385 m_framebufferExplorer->set_framebuffer_objects(framebufferObjects, *pContext, pStateSnapshot->get_default_framebuffer());
1386 GLuint64 curDrawFramebuffer = pContext->get_general_state().get_value<GLuint64>(GL_DRAW_FRAMEBUFFER_BINDING);
1387 displayFramebuffer(curDrawFramebuffer, false);
1390 vogl_gl_object_state_ptr_vec programObjects;
1391 pContext->get_all_objects_of_category(cGLSTProgram, programObjects);
1392 m_programExplorer->set_program_objects(programObjects);
1393 GLuint64 curProgram = pContext->get_general_state().get_value<GLuint64>(GL_CURRENT_PROGRAM);
1394 m_programExplorer->set_active_program(curProgram);
1395 if (programObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->programTab); }
1398 vogl_gl_object_state_ptr_vec shaderObjects;
1399 pContext->get_all_objects_of_category(cGLSTShader, shaderObjects);
1400 m_shaderExplorer->set_shader_objects(shaderObjects);
1401 if (curProgram != 0)
1403 for (vogl_gl_object_state_ptr_vec::iterator iter = programObjects.begin(); iter != programObjects.end(); iter++)
1405 if ((*iter)->get_snapshot_handle() == curProgram)
1407 vogl_program_state* pProgramState = static_cast<vogl_program_state*>(*iter);
1408 if (pProgramState->get_attached_shaders().size() > 0)
1410 uint curShader = pProgramState->get_attached_shaders()[0];
1411 m_shaderExplorer->set_active_shader(curShader);
1417 if (shaderObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->shaderTab); }
1421 this->setCursor(origCursor);
1424 void VoglEditor::on_stateTreeView_clicked(const QModelIndex &index)
1426 vogleditor_stateTreeItem* pStateItem = static_cast<vogleditor_stateTreeItem*>(index.internalPointer());
1427 if (pStateItem == NULL)
1432 switch(pStateItem->getStateType())
1434 case vogleditor_stateTreeItem::cTEXTURE:
1436 vogleditor_stateTreeTextureItem* pTextureItem = static_cast<vogleditor_stateTreeTextureItem*>(pStateItem);
1437 if (pTextureItem == NULL)
1442 displayTexture(pTextureItem->get_texture_state()->get_snapshot_handle(), true);
1446 case vogleditor_stateTreeItem::cPROGRAM:
1448 vogleditor_stateTreeProgramItem* pProgramItem = static_cast<vogleditor_stateTreeProgramItem*>(pStateItem);
1449 if (pProgramItem == NULL)
1454 displayProgram(pProgramItem->get_current_state()->get_snapshot_handle(), true);
1458 case vogleditor_stateTreeItem::cSHADER:
1460 vogleditor_stateTreeShaderItem* pShaderItem = static_cast<vogleditor_stateTreeShaderItem*>(pStateItem);
1461 if (pShaderItem == NULL)
1466 displayShader(pShaderItem->get_current_state()->get_snapshot_handle(), true);
1470 case vogleditor_stateTreeItem::cFRAMEBUFFER:
1472 vogleditor_stateTreeFramebufferItem* pFramebufferItem = static_cast<vogleditor_stateTreeFramebufferItem*>(pStateItem);
1473 if (pFramebufferItem == NULL)
1478 displayFramebuffer(pFramebufferItem->get_framebuffer_state()->get_snapshot_handle(), true);
1482 case vogleditor_stateTreeItem::cDEFAULT:
1489 bool VoglEditor::displayShader(GLuint64 shaderHandle, bool bBringTabToFront)
1491 bool bDisplayed = false;
1492 if (m_shaderExplorer->set_active_shader(shaderHandle))
1494 if (bBringTabToFront)
1496 ui->tabWidget->setCurrentWidget(ui->shaderTab);
1503 void VoglEditor::displayProgram(GLuint64 programHandle, bool bBringTabToFront)
1505 if (m_programExplorer->set_active_program(programHandle))
1507 if (bBringTabToFront)
1509 ui->tabWidget->setCurrentWidget(ui->programTab);
1514 void VoglEditor::displayFramebuffer(GLuint64 framebufferHandle, bool bBringTabToFront)
1516 bool bDisplayedFBO = m_framebufferExplorer->set_active_framebuffer(framebufferHandle);
1520 VOGLEDITOR_ENABLE_TAB(ui->framebufferTab);
1521 if (bBringTabToFront)
1523 ui->tabWidget->setCurrentWidget(ui->framebufferTab);
1528 bool VoglEditor::displayTexture(GLuint64 textureHandle, bool bBringTabToFront)
1530 bool bDisplayedTexture = m_textureExplorer->set_active_texture(textureHandle);
1532 if (bDisplayedTexture)
1534 VOGLEDITOR_ENABLE_TAB(ui->textureTab);
1535 if (bBringTabToFront)
1537 ui->tabWidget->setCurrentWidget(ui->textureTab);
1541 return bDisplayedTexture;
1544 void VoglEditor::on_treeView_currentChanged(const QModelIndex & current, const QModelIndex & previous)
1546 VOGL_NOTE_UNUSED(previous);
1547 onApiCallSelected(current, false);
1550 void VoglEditor::on_treeView_clicked(const QModelIndex &index)
1552 onApiCallSelected(index, true);
1555 void VoglEditor::onApiCallSelected(const QModelIndex &index, bool bAllowStateSnapshot)
1557 vogleditor_apiCallTreeItem* pCallTreeItem = static_cast<vogleditor_apiCallTreeItem*>(index.internalPointer());
1558 if (pCallTreeItem == NULL)
1563 vogleditor_frameItem* pFrameItem = pCallTreeItem->frameItem();
1564 vogleditor_apiCallItem* pApiCallItem = pCallTreeItem->apiCallItem();
1566 if (bAllowStateSnapshot && pCallTreeItem == m_pCurrentCallTreeItem)
1568 // we can only get snapshots for specific API calls
1569 if (pApiCallItem != NULL && pApiCallItem->needs_snapshot())
1571 // get the snapshot after the current api call
1572 vogleditor_gl_state_snapshot* pNewSnapshot = NULL;
1573 QCursor origCursor = cursor();
1574 setCursor(Qt::WaitCursor);
1575 m_traceReplayer.replay(m_pTraceReader, m_pApicallTreeModel->root(), &pNewSnapshot, pApiCallItem->globalCallIndex(), false);
1576 setCursor(origCursor);
1577 pCallTreeItem->set_snapshot(pNewSnapshot);
1581 update_ui_for_snapshot(pCallTreeItem->get_snapshot());
1583 if (pApiCallItem != NULL && m_pCurrentCallTreeItem != pCallTreeItem)
1585 if (m_backtraceToJsonMap.size() > 0)
1588 json_node* pBacktraceNode = m_backtraceToJsonMap[(uint)pApiCallItem->backtraceHashIndex()];
1589 if (pBacktraceNode != NULL)
1591 json_node* pAddrs = pBacktraceNode->find_child_array("addrs");
1592 json_node* pSyms = pBacktraceNode->find_child_array("syms");
1594 for (uint i = 0; i < pAddrs->size(); i++)
1596 tmp += pAddrs->get_value(i).as_string_ptr();
1600 tmp += pSyms->get_value(i).as_string_ptr();
1605 ui->backtraceText->setText(tmp);
1609 if (pApiCallItem != NULL)
1611 m_timeline->setCurrentApiCall(pApiCallItem->globalCallIndex());
1614 if (pFrameItem != NULL)
1616 m_timeline->setCurrentFrame(pFrameItem->frameNumber());
1619 m_timeline->repaint();
1621 m_pCurrentCallTreeItem = pCallTreeItem;
1624 void VoglEditor::selectApicallModelIndex(QModelIndex index, bool scrollTo, bool select)
1626 // make sure the index is visible
1627 QModelIndex parentIndex = index.parent();
1628 while (parentIndex.isValid())
1630 if (ui->treeView->isExpanded(parentIndex) == false)
1632 ui->treeView->expand(parentIndex);
1634 parentIndex = parentIndex.parent();
1637 // scroll to the index
1640 ui->treeView->scrollTo(index);
1646 ui->treeView->setCurrentIndex(index);
1649 if (m_searchApicallResults.size() > 0 && !ui->searchTextBox->text().isEmpty())
1651 QItemSelectionModel* pSelection = ui->treeView->selectionModel();
1652 for (int i = 0; i < m_searchApicallResults.size(); i++)
1654 pSelection->select(m_searchApicallResults[i], QItemSelectionModel::Select | QItemSelectionModel::Rows);
1656 ui->treeView->setSelectionModel(pSelection);
1660 void VoglEditor::on_searchTextBox_textChanged(const QString &searchText)
1662 QModelIndex curSearchIndex = ui->treeView->currentIndex();
1663 if (curSearchIndex.isValid() == false)
1668 // store original background color of the search text box so that it can be turned to red and later restored.
1669 static const QColor sOriginalTextBoxBackground = ui->searchTextBox->palette().base().color();
1671 // clear previous items
1672 QItemSelectionModel* pSelection = ui->treeView->selectionModel();
1673 if (pSelection != NULL)
1675 for (int i = 0; i < m_searchApicallResults.size(); i++)
1677 pSelection->select(m_searchApicallResults[i], QItemSelectionModel::Clear | QItemSelectionModel::Rows);
1679 ui->treeView->setSelectionModel(pSelection);
1683 m_searchApicallResults.clear();
1684 if (m_pApicallTreeModel != NULL)
1686 m_searchApicallResults = m_pApicallTreeModel->find_search_matches(searchText);
1689 // if there are matches, restore the textbox background to its original color
1690 if (m_searchApicallResults.size() > 0)
1692 QPalette palette(ui->searchTextBox->palette());
1693 palette.setColor(QPalette::Base, sOriginalTextBoxBackground);
1694 ui->searchTextBox->setPalette(palette);
1698 if (!searchText.isEmpty())
1700 if (m_searchApicallResults.size() > 0)
1702 // scroll to the first result, but don't select it
1703 selectApicallModelIndex(m_searchApicallResults[0], true, false);
1707 // no items were found, so set the textbox background to red
1708 QPalette palette(ui->searchTextBox->palette());
1709 palette.setColor(QPalette::Base, Qt::red);
1710 ui->searchTextBox->setPalette(palette);
1715 void VoglEditor::on_searchNextButton_clicked()
1717 if (m_pApicallTreeModel != NULL)
1719 QModelIndex index = m_pApicallTreeModel->find_next_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
1720 selectApicallModelIndex(index, true, true);
1724 void VoglEditor::on_searchPrevButton_clicked()
1726 if (m_pApicallTreeModel != NULL)
1728 QModelIndex index = m_pApicallTreeModel->find_prev_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
1729 selectApicallModelIndex(index, true, true);
1733 void VoglEditor::on_prevSnapshotButton_clicked()
1735 if (m_pApicallTreeModel != NULL)
1737 vogleditor_apiCallTreeItem* pPrevItemWithSnapshot = m_pApicallTreeModel->find_prev_snapshot(m_pCurrentCallTreeItem);
1738 selectApicallModelIndex(m_pApicallTreeModel->indexOf(pPrevItemWithSnapshot), true, true);
1739 ui->treeView->setFocus();
1743 void VoglEditor::on_nextSnapshotButton_clicked()
1745 if (m_pApicallTreeModel != NULL)
1747 vogleditor_apiCallTreeItem* pNextItemWithSnapshot = m_pApicallTreeModel->find_next_snapshot(m_pCurrentCallTreeItem);
1748 selectApicallModelIndex(m_pApicallTreeModel->indexOf(pNextItemWithSnapshot), true, true);
1749 ui->treeView->setFocus();
1753 void VoglEditor::on_prevDrawcallButton_clicked()
1755 if (m_pApicallTreeModel != NULL)
1757 vogleditor_apiCallTreeItem* pPrevItem = m_pApicallTreeModel->find_prev_drawcall(m_pCurrentCallTreeItem);
1758 selectApicallModelIndex(m_pApicallTreeModel->indexOf(pPrevItem), true, true);
1759 ui->treeView->setFocus();
1763 void VoglEditor::on_nextDrawcallButton_clicked()
1765 if (m_pApicallTreeModel != NULL)
1767 vogleditor_apiCallTreeItem* pNextItem = m_pApicallTreeModel->find_next_drawcall(m_pCurrentCallTreeItem);
1768 selectApicallModelIndex(m_pApicallTreeModel->indexOf(pNextItem), true, true);
1769 ui->treeView->setFocus();
1774 void VoglEditor::on_program_edited(vogl_program_state* pNewProgramState)
1776 VOGL_NOTE_UNUSED(pNewProgramState);
1778 m_currentSnapshot->set_edited(true);
1780 // update all the snapshot flags
1781 bool bFoundEditedSnapshot = false;
1782 recursive_update_snapshot_flags(m_pApicallTreeModel->root(), bFoundEditedSnapshot);
1784 // 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,
1785 // 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
1786 // 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
1787 // API call instead of cycling through state options).
1788 ui->treeView->setFocus();
1791 // if an edited snapshot has already been found, mark the node (and all children) as dirty.
1792 void VoglEditor::recursive_update_snapshot_flags(vogleditor_apiCallTreeItem* pItem, bool& bFoundEditedSnapshot)
1794 // check if this item has a snapshot shot
1795 if (pItem->has_snapshot())
1797 if (!bFoundEditedSnapshot)
1799 if (pItem->get_snapshot()->is_edited())
1801 bFoundEditedSnapshot = true;
1805 pItem->get_snapshot()->set_outdated(false);
1810 pItem->get_snapshot()->set_outdated(true);
1814 for (int i = 0; i < pItem->childCount(); i++)
1816 recursive_update_snapshot_flags(pItem->child(i), bFoundEditedSnapshot);
1820 #undef VOGLEDITOR_DISABLE_TAB
1821 #undef VOGLEDITOR_ENABLE_TAB
1823 void VoglEditor::on_actionSave_Session_triggered()
1825 QString baseName = m_openFilename;
1827 int lastIndex = baseName.lastIndexOf('.');
1828 if (lastIndex != -1)
1830 baseName = baseName.remove(lastIndex, baseName.size() - lastIndex);
1833 QString suggestedName = baseName + "-vogleditor.json";
1835 QString sessionFilename = QFileDialog::getSaveFileName(this, tr("Save Debug Session"), suggestedName, tr("JSON (*.json)"));
1837 if (!save_session_to_disk(sessionFilename))
1839 m_statusLabel->setText("ERROR: Failed to save session");
1843 void VoglEditor::on_actionOpen_Session_triggered()
1845 QString sessionFilename = QFileDialog::getOpenFileName(this, tr("Load Debug Session"), QString(), tr("JSON (*.json)"));
1847 QCursor origCursor = this->cursor();
1848 setCursor(Qt::WaitCursor);
1850 if (!load_session_from_disk(sessionFilename))
1852 m_statusLabel->setText("ERROR: Failed to load session");
1855 setCursor(origCursor);