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>
31 #include <QSortFilterProxyModel>
32 #include <QSpacerItem>
33 #include <QToolButton>
34 #include <QMessageBox>
36 #include "ui_vogleditor.h"
37 #include "vogleditor.h"
39 #include "vogleditor_qapicalltreemodel.h"
40 #include "vogleditor_apicalltimelinemodel.h"
42 #include "vogl_platform.h"
43 #include "vogl_assert.h"
44 #include "vogl_file_utils.h"
45 #include "vogl_find_files.h"
47 #include "vogl_texture_format.h"
48 #include "vogl_trace_file_reader.h"
49 #include "vogl_trace_file_writer.h"
50 #include "vogleditor_output.h"
51 #include "vogleditor_settings.h"
52 #include "vogleditor_statetreetextureitem.h"
53 #include "vogleditor_statetreeprogramitem.h"
54 #include "vogleditor_statetreeshaderitem.h"
55 #include "vogleditor_statetreeframebufferitem.h"
56 #include "vogleditor_qstatetreemodel.h"
57 #include "vogleditor_qtextureexplorer.h"
58 #include "vogleditor_qtrimdialog.h"
60 #define VOGLEDITOR_DISABLE_STATE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), false);
61 #define VOGLEDITOR_ENABLE_STATE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), true);
63 #define VOGLEDITOR_DISABLE_BOTTOM_TAB(tab) ui->bottomTabWidget->setTabEnabled(ui->bottomTabWidget->indexOf(tab), false);
64 #define VOGLEDITOR_ENABLE_BOTTOM_TAB(tab) ui->bottomTabWidget->setTabEnabled(ui->bottomTabWidget->indexOf(tab), true);
66 //----------------------------------------------------------------------------------------------------------------------
68 //----------------------------------------------------------------------------------------------------------------------
69 static void *g_actual_libgl_module_handle;
70 static QString g_PROJECT_NAME = "Vogl Editor";
71 static vogleditor_settings g_settings;
72 static const char* g_SETTINGS_FILE = "./vogleditor_settings.json";
74 //----------------------------------------------------------------------------------------------------------------------
75 // vogl_get_proc_address_helper
76 //----------------------------------------------------------------------------------------------------------------------
77 static vogl_void_func_ptr_t vogl_get_proc_address_helper(const char *pName)
81 vogl_void_func_ptr_t pFunc = g_actual_libgl_module_handle ? reinterpret_cast<vogl_void_func_ptr_t>(dlsym(g_actual_libgl_module_handle, pName)) : NULL;
83 if ((!pFunc) && (GL_ENTRYPOINT(glXGetProcAddress)))
84 pFunc = reinterpret_cast<vogl_void_func_ptr_t>( GL_ENTRYPOINT(glXGetProcAddress)(reinterpret_cast<const GLubyte*>(pName)) );
90 //----------------------------------------------------------------------------------------------------------------------
92 //----------------------------------------------------------------------------------------------------------------------
97 g_actual_libgl_module_handle = dlopen("libGL.so.1", RTLD_LAZY);
98 if (!g_actual_libgl_module_handle)
100 vogl_error_printf("%s: Failed loading libGL.so.1!\n", VOGL_FUNCTION_NAME);
104 GL_ENTRYPOINT(glXGetProcAddress) = reinterpret_cast<glXGetProcAddress_func_ptr_t>(dlsym(g_actual_libgl_module_handle, "glXGetProcAddress"));
105 if (!GL_ENTRYPOINT(glXGetProcAddress))
107 vogl_error_printf("%s: Failed getting address of glXGetProcAddress() from libGL.so.1!\n", VOGL_FUNCTION_NAME);
114 VoglEditor::VoglEditor(QWidget *parent) :
116 ui(new Ui::VoglEditor),
117 m_pFramebufferExplorer(NULL),
118 m_pTextureExplorer(NULL),
119 m_pRenderbufferExplorer(NULL),
120 m_pProgramExplorer(NULL),
121 m_pShaderExplorer(NULL),
123 m_pFramebufferTab_layout(NULL),
124 m_pTextureTab_layout(NULL),
125 m_pRenderbufferTab_layout(NULL),
126 m_pProgramTab_layout(NULL),
127 m_pShaderTab_layout(NULL),
128 m_currentSnapshot(NULL),
129 m_pCurrentCallTreeItem(NULL),
130 m_pVoglReplayProcess(new QProcess()),
133 m_pTraceReader(NULL),
134 m_pTimelineModel(NULL),
135 m_pApiCallTreeModel(NULL),
136 m_pStateTreeModel(NULL)
142 vogl_init_actual_gl_entrypoints(vogl_get_proc_address_helper);
145 // load the settings file. This will only succeed if the file already exists
146 g_settings.load(g_SETTINGS_FILE);
148 // always save/resave the file wiill either be created or so that new settings will be added
149 g_settings.save(g_SETTINGS_FILE);
151 this->move(g_settings.window_position_left(), g_settings.window_position_top());
152 this->resize(g_settings.window_size_width(), g_settings.window_size_height());
154 vogleditor_output_init(ui->outputTextEdit);
155 vogleditor_output_message("Welcome to VoglEditor!");
157 // cache the original background color of the search text box
158 m_searchTextboxBackgroundColor = ui->searchTextBox->palette().base().color();
160 // setup framebuffer tab
161 m_pFramebufferTab_layout = new QGridLayout();
162 m_pFramebufferExplorer = new vogleditor_QFramebufferExplorer(ui->framebufferTab);
163 m_pFramebufferTab_layout->addWidget(m_pFramebufferExplorer, 0, 0);
164 ui->framebufferTab->setLayout(m_pFramebufferTab_layout);
167 m_pTextureTab_layout = new QGridLayout();
168 m_pTextureExplorer = new vogleditor_QTextureExplorer(ui->textureTab);
169 m_pTextureTab_layout->addWidget(m_pTextureExplorer, 0, 0);
170 ui->textureTab->setLayout(m_pTextureTab_layout);
172 // setup renderbuffer tab
173 m_pRenderbufferTab_layout = new QGridLayout();
174 m_pRenderbufferExplorer = new vogleditor_QTextureExplorer(ui->renderbufferTab);
175 m_pRenderbufferTab_layout->addWidget(m_pRenderbufferExplorer, 0, 0);
176 ui->renderbufferTab->setLayout(m_pRenderbufferTab_layout);
179 m_pProgramTab_layout = new QGridLayout();
180 m_pProgramExplorer = new vogleditor_QProgramExplorer(ui->programTab);
181 m_pProgramTab_layout->addWidget(m_pProgramExplorer, 0, 0);
182 ui->programTab->setLayout(m_pProgramTab_layout);
185 m_pShaderTab_layout = new QGridLayout();
186 m_pShaderExplorer = new vogleditor_QShaderExplorer(ui->shaderTab);
187 m_pShaderTab_layout->addWidget(m_pShaderExplorer, 0, 0);
188 ui->shaderTab->setLayout(m_pShaderTab_layout);
191 m_timeline = new vogleditor_QTimelineView();
192 m_timeline->setMinimumHeight(100);
193 ui->verticalLayout->addWidget(m_timeline);
194 ui->verticalLayout->removeWidget(ui->timelineViewPlaceholder);
196 // add buttons to toolbar
197 m_pPlayButton = new QToolButton(ui->mainToolBar);
198 m_pPlayButton->setText("Play Trace");
199 m_pPlayButton->setEnabled(false);
201 m_pTrimButton = new QToolButton(ui->mainToolBar);
202 m_pTrimButton->setText("Trim Trace");
203 m_pTrimButton->setEnabled(false);
205 ui->mainToolBar->addWidget(m_pPlayButton);
206 ui->mainToolBar->addWidget(m_pTrimButton);
208 connect(m_pPlayButton, SIGNAL(clicked()), this, SLOT(playCurrentTraceFile()));
209 connect(m_pTrimButton, SIGNAL(clicked()), this, SLOT(trimCurrentTraceFile()));
211 connect(m_pProgramExplorer, SIGNAL(program_edited(vogl_program_state*)), this, SLOT(slot_program_edited(vogl_program_state*)));
213 connect(m_pVoglReplayProcess, SIGNAL(readyReadStandardOutput()), this, SLOT(slot_readReplayStandardOutput()));
214 connect(m_pVoglReplayProcess, SIGNAL(readyReadStandardError()), this, SLOT(slot_readReplayStandardError()));
216 reset_tracefile_ui();
219 VoglEditor::~VoglEditor()
221 // update any settings and save the settings file
222 g_settings.set_window_position_left(this->x());
223 g_settings.set_window_position_top(this->y());
224 g_settings.set_window_size_width(this->width());
225 g_settings.set_window_size_height(this->height());
226 g_settings.save(g_SETTINGS_FILE);
230 vogleditor_output_deinit();
232 if (m_pFramebufferExplorer != NULL)
234 delete m_pFramebufferExplorer;
235 m_pFramebufferExplorer = NULL;
238 if (m_pTextureExplorer != NULL)
240 delete m_pTextureExplorer;
241 m_pTextureExplorer = NULL;
244 if (m_pRenderbufferExplorer != NULL)
246 delete m_pRenderbufferExplorer;
247 m_pRenderbufferExplorer = NULL;
250 if (m_pProgramExplorer != NULL)
252 delete m_pProgramExplorer;
253 m_pProgramExplorer = NULL;
256 if (m_pShaderExplorer != NULL)
258 delete m_pShaderExplorer;
259 m_pShaderExplorer = NULL;
262 if (m_pPlayButton != NULL)
264 delete m_pPlayButton;
265 m_pPlayButton = NULL;
268 if (m_pTrimButton != NULL)
270 delete m_pTrimButton;
271 m_pTrimButton = NULL;
274 if (m_pFramebufferTab_layout != NULL)
276 delete m_pFramebufferTab_layout;
277 m_pFramebufferTab_layout = NULL;
280 if (m_pTextureTab_layout != NULL)
282 delete m_pTextureTab_layout;
283 m_pTextureTab_layout = NULL;
286 if (m_pRenderbufferTab_layout != NULL)
288 delete m_pRenderbufferTab_layout;
289 m_pRenderbufferTab_layout = NULL;
292 if (m_pProgramTab_layout != NULL)
294 delete m_pProgramTab_layout;
295 m_pProgramTab_layout = NULL;
298 if (m_pShaderTab_layout != NULL)
300 delete m_pShaderTab_layout;
301 m_pShaderTab_layout = NULL;
304 if (m_pStateTreeModel != NULL)
306 delete m_pStateTreeModel;
307 m_pStateTreeModel = NULL;
310 if (m_pVoglReplayProcess != NULL)
312 delete m_pVoglReplayProcess;
313 m_pVoglReplayProcess = NULL;
317 void VoglEditor::playCurrentTraceFile()
319 QCursor origCursor = cursor();
320 setCursor(Qt::WaitCursor);
323 m_pPlayButton->setEnabled(false);
324 m_pTrimButton->setEnabled(false);
326 if (m_traceReplayer.replay(m_pTraceReader, m_pApiCallTreeModel->root(), NULL, 0, true))
328 // replay was successful
329 m_pPlayButton->setEnabled(true);
330 m_pTrimButton->setEnabled(true);
334 vogleditor_output_error("Failed to replay the trace.");
337 setCursor(origCursor);
340 void VoglEditor::trimCurrentTraceFile()
342 trim_trace_file(m_openFilename, static_cast<uint>(m_pTraceReader->get_max_frame_index()), g_settings.trim_large_trace_prompt_size());
345 /// \return True if the new trim file is now open in the editor
346 /// \return False if there was an error, or the user elected NOT to open the new trim file
347 bool VoglEditor::trim_trace_file(QString filename, uint maxFrameIndex, uint maxAllowedTrimLen)
349 // open a dialog to gather parameters for the replayer
350 vogleditor_QTrimDialog trimDialog(filename, maxFrameIndex, maxAllowedTrimLen, this);
351 int code = trimDialog.exec();
353 if (code == QDialog::Rejected)
358 QStringList arguments;
359 arguments << "--trim_frame" << trimDialog.trim_frame() << "--trim_len" << trimDialog.trim_len() << "--trim_file" << trimDialog.trim_file() << filename;
362 QString executable = "./voglreplay32";
364 QString executable = "./voglreplay64";
367 QString cmdLine = executable + " " + arguments.join(" ");
369 vogleditor_output_message("Trimming trace file");
370 vogleditor_output_message(cmdLine.toStdString().c_str());
371 m_pVoglReplayProcess->start(executable, arguments);
372 if (m_pVoglReplayProcess->waitForStarted() == false)
374 vogleditor_output_error("voglreplay could not be executed.");
378 // This is a bad idea as it will wait forever,
379 // but if the replay is taking forever then we have bigger problems.
380 if(m_pVoglReplayProcess->waitForFinished(-1))
382 vogleditor_output_message("Trim Completed!");
385 int procRetValue = m_pVoglReplayProcess->exitCode();
387 bool bCompleted = false;
388 if (procRetValue == -2)
390 // proc failed to starts
391 vogleditor_output_error("voglreplay could not be executed.");
393 else if (procRetValue == -1)
396 vogleditor_output_error("voglreplay aborted unexpectedly.");
398 else if (procRetValue == 0)
405 // some other return value
411 int ret = QMessageBox::warning(this, tr("Trim Trace"), tr("Would you like to load the new trimmed trace file?"),
412 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
414 if (ret == QMessageBox::Yes)
417 if (open_trace_file(trimDialog.trim_file().toStdString().c_str()))
423 vogleditor_output_error("Could not open trace file.");
424 QMessageBox::critical(this, tr("Error"), tr("Could not open trace file."));
430 vogleditor_output_error("Failed to trim the trace file.");
431 QMessageBox::critical(this, tr("Error"), tr("Failed to trim the trace file."));
436 void VoglEditor::on_actionE_xit_triggered()
441 void VoglEditor::on_action_Open_triggered()
443 QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), QString(),
444 tr("VOGL Binary Files (*.bin);;VOGL JSON Files (*.json)"));
446 if (!fileName.isEmpty()) {
447 vogl::dynamic_string filename;
448 filename.set(fileName.toStdString().c_str());
450 if (open_trace_file(filename) == false) {
451 QMessageBox::critical(this, tr("Error"), tr("Could not open trace file."));
457 void VoglEditor::on_action_Close_triggered()
462 void VoglEditor::close_trace_file()
464 if (m_pTraceReader != NULL)
466 vogleditor_output_message("Closing trace file.");
467 vogleditor_output_message("-------------------");
468 m_pTraceReader->close();
469 vogl_delete(m_pTraceReader);
470 m_pTraceReader = NULL;
472 setWindowTitle(g_PROJECT_NAME);
474 m_openFilename.clear();
475 m_backtraceToJsonMap.clear();
476 m_backtraceDoc.clear();
478 reset_tracefile_ui();
480 ui->treeView->setModel(NULL);
481 ui->machineInfoText->clear();
482 ui->backtraceText->clear();
483 m_timeline->setModel(NULL);
484 m_timeline->repaint();
486 if (m_pTimelineModel != NULL)
488 delete m_pTimelineModel;
489 m_pTimelineModel = NULL;
492 if (m_pApiCallTreeModel != NULL)
494 delete m_pApiCallTreeModel;
495 m_pApiCallTreeModel = NULL;
500 void VoglEditor::write_child_api_calls(vogleditor_apiCallTreeItem* pItem, FILE* pFile)
502 QString string = pItem->columnData(VOGL_ACTC_APICALL, Qt::DisplayRole).toString();
503 vogl_fwrite(string.toStdString().c_str(), 1, string.size(), pFile);
504 vogl_fwrite("\r\n", 1, 2, pFile);
506 for (int i = 0; i < pItem->childCount(); i++)
508 write_child_api_calls(pItem->child(i), pFile);
512 void VoglEditor::on_actionExport_API_Calls_triggered()
514 QString suggestedName = m_openFilename;
516 int lastIndex = suggestedName.lastIndexOf('-');
519 suggestedName = suggestedName.remove(lastIndex, suggestedName.size() - lastIndex);
521 suggestedName += "-ApiCalls.txt";
523 QString fileName = QFileDialog::getSaveFileName(this, tr("Export API Calls"), suggestedName, tr("Text (*.txt)"));
525 if (!fileName.isEmpty())
527 vogl::dynamic_string filename;
528 filename.set(fileName.toStdString().c_str());
530 FILE* pFile = vogl_fopen(filename.c_str(), "w");
531 vogleditor_QApiCallTreeModel* pModel = static_cast<vogleditor_QApiCallTreeModel*>(ui->treeView->model());
532 vogleditor_apiCallTreeItem* pRoot = pModel->root();
533 for (int i = 0; i < pRoot->childCount(); i++)
535 write_child_api_calls(pRoot->child(i), pFile);
541 static const unsigned int VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1 = 1;
542 static const unsigned int VOGLEDITOR_SESSION_FILE_FORMAT_VERSION = VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1;
544 bool VoglEditor::load_session_from_disk(QString sessionFile)
547 json_document sessionDoc;
548 if (!sessionDoc.deserialize_file(sessionFile.toStdString().c_str()))
553 // look for expected metadata
554 json_node* pMetadata = sessionDoc.get_root()->find_child_object("metadata");
555 if (pMetadata == NULL)
560 const json_value& rFormatVersion = pMetadata->find_value("session_file_format_version");
561 if (!rFormatVersion.is_valid())
566 if (rFormatVersion.as_uint32() != VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1)
571 // load base trace file
572 json_node* pBaseTraceFile = sessionDoc.get_root()->find_child_object("base_trace_file");
573 if (pBaseTraceFile == NULL)
578 const json_value& rBaseTraceFilePath = pBaseTraceFile->find_value("rel_path");
579 const json_value& rBaseTraceFileUuid = pBaseTraceFile->find_value("uuid");
581 if (!rBaseTraceFilePath.is_valid() || !rBaseTraceFileUuid.is_valid())
586 dynamic_string sessionPathName;
587 dynamic_string sessionFileName;
588 file_utils::split_path(sessionFile.toStdString().c_str(), sessionPathName, sessionFileName);
590 dynamic_string traceFilePath = sessionPathName;
591 traceFilePath.append(rBaseTraceFilePath.as_string());
593 if (!open_trace_file(traceFilePath))
598 // TODO: verify UUID of the loaded trace file
600 // load session data if it is available
601 json_node* pSessionData = sessionDoc.get_root()->find_child_object("session_data");
602 if (pSessionData != NULL)
604 const json_value& rSessionPath = pSessionData->find_value("rel_path");
605 if (!rSessionPath.is_valid())
610 dynamic_string sessionDataPath = sessionPathName;
611 sessionDataPath.append(rSessionPath.as_string());
613 vogl_loose_file_blob_manager file_blob_manager;
614 file_blob_manager.init(cBMFReadWrite, sessionDataPath.c_str());
615 vogl_blob_manager* pBlob_manager = static_cast<vogl_blob_manager*>(&file_blob_manager);
618 const json_node* pSnapshots = pSessionData->find_child_array("snapshots");
619 for (unsigned int i = 0; i < pSnapshots->size(); i++)
621 const json_node* pSnapshotNode = pSnapshots->get_value_as_object(i);
623 const json_value& uuid = pSnapshotNode->find_value("uuid");
624 const json_value& isValid = pSnapshotNode->find_value("is_valid");
625 const json_value& isEdited = pSnapshotNode->find_value("is_edited");
626 const json_value& isOutdated = pSnapshotNode->find_value("is_outdated");
627 const json_value& frameNumber = pSnapshotNode->find_value("frame_number");
628 const json_value& callIndex = pSnapshotNode->find_value("call_index");
629 const json_value& path = pSnapshotNode->find_value("rel_path");
631 // make sure expected nodes are valid
632 if (!isValid.is_valid() || !isEdited.is_valid() || !isOutdated.is_valid())
637 vogl_gl_state_snapshot* pSnapshot = NULL;
639 if (path.is_valid() && isValid.as_bool() && uuid.is_valid())
641 dynamic_string snapshotPath = sessionDataPath;
642 snapshotPath.append(path.as_string());
645 json_document snapshotDoc;
646 if (!snapshotDoc.deserialize_file(snapshotPath.c_str()))
651 // attempt to verify the snapshot file
652 json_node* pSnapshotRoot = snapshotDoc.get_root();
653 if (pSnapshotRoot == NULL)
655 vogl_warning_printf("Invalid snapshot file at %s.", path.as_string_ptr());
659 const json_value& snapshotUuid = pSnapshotRoot->find_value("uuid");
660 if (!snapshotUuid.is_valid())
662 vogl_warning_printf("Invalid 'uuid' in snapshot file at %s.", path.as_string_ptr());
666 if (snapshotUuid.as_string() != uuid.as_string())
668 vogl_warning_printf("Mismatching 'uuid' between snapshot file at %s and that stored in the session file at %s.", path.as_string_ptr(), sessionFile.toStdString().c_str());
672 vogl_ctypes trace_gl_ctypes(m_pTraceReader->get_sof_packet().m_pointer_sizes);
673 pSnapshot = vogl_new(vogl_gl_state_snapshot);
674 if (!pSnapshot->deserialize(*snapshotDoc.get_root(), *pBlob_manager, &trace_gl_ctypes))
676 vogl_delete(pSnapshot);
678 vogl_warning_printf("Unable to deserialize the snapshot with uuid %s.", uuid.as_string_ptr());
683 vogleditor_gl_state_snapshot* pContainer = vogl_new(vogleditor_gl_state_snapshot, pSnapshot);
684 pContainer->set_edited(isEdited.as_bool());
685 pContainer->set_outdated(isOutdated.as_bool());
687 if (callIndex.is_valid())
689 // the snapshot is associated with an api call
690 vogleditor_apiCallTreeItem* pItem = m_pApiCallTreeModel->find_call_number(callIndex.as_uint64());
693 pItem->set_snapshot(pContainer);
697 vogl_warning_printf("Unable to find API call index %" PRIu64 " to load the snapshot into.", callIndex.as_uint64());
698 if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
699 if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
702 else if (frameNumber.is_valid())
704 // the snapshot is associated with a frame.
705 // frame snapshots have the additional requirement that the snapshot itself MUST exist since
706 // we only save a frame snapshot if it is the inital frame and it has been edited.
707 // If we allow NULL snapshots, that we could accidently remove the initial snapshot that was loaded with the trace file.
708 if (pSnapshot != NULL)
710 vogleditor_apiCallTreeItem* pItem = m_pApiCallTreeModel->find_frame_number(frameNumber.as_uint64());
713 pItem->set_snapshot(pContainer);
717 vogl_warning_printf("Unable to find frame number %" PRIu64 " to load the snapshot into.", frameNumber.as_uint64());
718 if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
719 if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
725 vogl_warning_printf("Session file contains invalid call or frame number for snapshot with uuid %s", uuid.as_string_ptr());
726 if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; }
727 if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; }
736 * Below is a summary of the information that needs to be saved out in a session's json file so that we can reload the session and be fully-featured.
737 * Note that not all of this information is currently supported (either by VoglEditor or the save/load functionality).
739 * sample data structure for version 1:
742 "session_file_format_version" : "0x1" <- would need to be updated when organization of existing data is changed
744 "base_trace_file" : {
745 "path" : "../traces/trimmed4.bin",
746 "uuid" : [ 2761638124, 1361789091, 2623121922, 1789156619 ]
749 "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/",
752 "uuid" : "B346B680801ED2F5144E421DEA5EFDCC",
755 "is_outdated" : false,
759 "uuid" : "BC261B884088DBEADF376A03A489F2B9",
762 "is_outdated" : false,
763 "call_index" : 881069,
764 "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/snapshot_call_881069.json"
767 "uuid" : "176DE3DEAA437B871FE122C84D5432E3",
770 "is_outdated" : false,
771 "call_index" : 881075,
772 "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/snapshot_call_881075.json"
777 "is_outdated" : true,
778 "call_index" : 881080
784 bool VoglEditor::save_session_to_disk(QString sessionFile)
786 dynamic_string sessionPathName;
787 dynamic_string sessionFileName;
788 file_utils::split_path(sessionFile.toStdString().c_str(), sessionPathName, sessionFileName);
790 // modify the session file name to make a sessiondata folder
791 QString sessionDataFolder(sessionFileName.c_str());
792 int lastIndex = sessionDataFolder.lastIndexOf('.');
795 sessionDataFolder = sessionDataFolder.remove(lastIndex, sessionDataFolder.size() - lastIndex);
797 sessionDataFolder += "-sessiondata/";
799 dynamic_string sessionDataPath = sessionPathName;
800 sessionDataPath.append(sessionDataFolder.toStdString().c_str());
801 file_utils::create_directories(sessionDataPath, false);
803 vogl_loose_file_blob_manager file_blob_manager;
804 file_blob_manager.init(cBMFReadWrite, sessionDataPath.c_str());
805 vogl_blob_manager* pBlob_manager = static_cast<vogl_blob_manager*>(&file_blob_manager);
807 QCursor origCursor = this->cursor();
808 setCursor(Qt::WaitCursor);
810 json_document sessionDoc;
811 json_node& metadata = sessionDoc.get_root()->add_object("metadata");
812 metadata.add_key_value("session_file_format_version", to_hex_string(VOGLEDITOR_SESSION_FILE_FORMAT_VERSION));
814 // find relative path from session file to trace file
816 QString absoluteTracePath = relativeAppDir.absoluteFilePath(m_openFilename.toStdString().c_str());
817 QDir absoluteSessionFileDir(sessionPathName.c_str());
818 QString tracePathRelativeToSessionFile = absoluteSessionFileDir.relativeFilePath(absoluteTracePath);
820 json_node& baseTraceFile = sessionDoc.get_root()->add_object("base_trace_file");
821 baseTraceFile.add_key_value("rel_path", tracePathRelativeToSessionFile.toStdString().c_str());
822 json_node &uuid_array = baseTraceFile.add_array("uuid");
823 for (uint i = 0; i < VOGL_ARRAY_SIZE(m_pTraceReader->get_sof_packet().m_uuid); i++)
825 uuid_array.add_value(m_pTraceReader->get_sof_packet().m_uuid[i]);
828 json_node& sessionDataNode = sessionDoc.get_root()->add_object("session_data");
829 sessionDataNode.add_key_value("rel_path", sessionDataFolder.toStdString().c_str());
830 json_node& snapshotArray = sessionDataNode.add_array("snapshots");
832 vogleditor_apiCallTreeItem* pItem = m_pApiCallTreeModel->find_next_snapshot(NULL);
833 vogleditor_apiCallTreeItem* pLastItem = NULL;
834 bool bSavedSuccessfully = true;
835 while (pItem != pLastItem && pItem != NULL)
837 dynamic_string filename;
839 json_node& snapshotNode = snapshotArray.add_object();
840 if (pItem->get_snapshot()->get_snapshot() != NULL)
842 dynamic_string strUUID;
843 snapshotNode.add_key_value("uuid", pItem->get_snapshot()->get_snapshot()->get_uuid().get_string(strUUID));
845 snapshotNode.add_key_value("is_valid", pItem->get_snapshot()->is_valid());
846 snapshotNode.add_key_value("is_edited", pItem->get_snapshot()->is_edited());
847 snapshotNode.add_key_value("is_outdated", pItem->get_snapshot()->is_outdated());
849 if (pItem->apiCallItem() != NULL)
851 uint64_t callIndex = pItem->apiCallItem()->globalCallIndex();
852 snapshotNode.add_key_value("call_index", callIndex);
853 if (pItem->get_snapshot()->get_snapshot() != NULL)
855 filename = filename.format("snapshot_call_%" PRIu64 ".json", callIndex);
856 snapshotNode.add_key_value("rel_path", filename);
857 dynamic_string filepath = sessionDataPath;
858 filepath.append(filename);
859 if (!save_snapshot_to_disk(pItem->get_snapshot()->get_snapshot(), filepath, pBlob_manager))
861 bSavedSuccessfully = false;
866 else if (pItem->frameItem() != NULL)
868 // the first frame of a trim will have a snapshot.
869 // this should only be saved out if the snapshot has been edited
870 uint64_t frameNumber = pItem->frameItem()->frameNumber();
871 snapshotNode.add_key_value("frame_number", frameNumber);
872 if (pItem->get_snapshot()->is_edited())
874 filename = filename.format("snapshot_frame_%" PRIu64 ".json", frameNumber);
875 snapshotNode.add_key_value("rel_path", filename);
876 dynamic_string filepath = sessionDataPath;
877 filepath.append(filename);
878 if (!save_snapshot_to_disk(pItem->get_snapshot()->get_snapshot(), filepath, pBlob_manager))
880 bSavedSuccessfully = false;
887 pItem = m_pApiCallTreeModel->find_next_snapshot(pLastItem);
890 if (bSavedSuccessfully)
892 bSavedSuccessfully = sessionDoc.serialize_to_file(sessionFile.toStdString().c_str());
895 setCursor(origCursor);
897 return bSavedSuccessfully;
900 bool VoglEditor::save_snapshot_to_disk(vogl_gl_state_snapshot *pSnapshot, dynamic_string filename, vogl_blob_manager *pBlob_manager)
902 if (pSnapshot == NULL)
909 vogl_ctypes trace_gl_ctypes(m_pTraceReader->get_sof_packet().m_pointer_sizes);
911 if (!pSnapshot->serialize(*doc.get_root(), *pBlob_manager, &trace_gl_ctypes))
913 vogl_error_printf("Failed serializing state snapshot document!\n");
916 else if (!doc.serialize_to_file(filename.get_ptr(), true))
918 vogl_error_printf("Failed writing state snapshot to file \"%s\"!\n", filename.get_ptr());
923 vogl_printf("Successfully wrote JSON snapshot to file \"%s\"\n", filename.get_ptr());
929 //----------------------------------------------------------------------------------------------------------------------
930 // read_state_snapshot_from_trace
931 //----------------------------------------------------------------------------------------------------------------------
932 vogl_gl_state_snapshot* VoglEditor::read_state_snapshot_from_trace(vogl_trace_file_reader* pTrace_reader)
934 vogl_ctypes trace_gl_ctypes(pTrace_reader->get_sof_packet().m_pointer_sizes);
936 vogl_trace_packet keyframe_trace_packet(&trace_gl_ctypes);
938 pTrace_reader->seek_to_frame(0);
940 vogl_gl_state_snapshot *pSnapshot = NULL;
941 bool found_snapshot = false;
944 vogl_trace_file_reader::trace_file_reader_status_t read_status = pTrace_reader->read_next_packet();
946 if ((read_status != vogl_trace_file_reader::cOK) && (read_status != vogl_trace_file_reader::cEOF))
948 vogl_error_printf("%s: Failed reading from keyframe trace file!\n", VOGL_FUNCTION_NAME);
952 if ((read_status == vogl_trace_file_reader::cEOF) || (pTrace_reader->get_packet_type() == cTSPTEOF))
954 vogl_error_printf("%s: Failed finding state snapshot in keyframe file!\n", VOGL_FUNCTION_NAME);
958 if (pTrace_reader->get_packet_type() != cTSPTGLEntrypoint)
961 if (!keyframe_trace_packet.deserialize(pTrace_reader->get_packet_buf().get_ptr(), pTrace_reader->get_packet_buf().size(), false))
963 vogl_error_printf("%s: Failed parsing GL entrypoint packet in keyframe file\n", VOGL_FUNCTION_NAME);
967 const vogl_trace_gl_entrypoint_packet *pGL_packet = &pTrace_reader->get_packet<vogl_trace_gl_entrypoint_packet>();
968 gl_entrypoint_id_t entrypoint_id = static_cast<gl_entrypoint_id_t>(pGL_packet->m_entrypoint_id);
970 if (vogl_is_swap_buffers_entrypoint(entrypoint_id) || vogl_is_draw_entrypoint(entrypoint_id) || vogl_is_make_current_entrypoint(entrypoint_id))
972 vogl_error_printf("Failed finding state snapshot in keyframe file!\n");
976 switch (entrypoint_id)
978 case VOGL_ENTRYPOINT_glInternalTraceCommandRAD:
980 GLuint cmd = keyframe_trace_packet.get_param_value<GLuint>(0);
981 GLuint size = keyframe_trace_packet.get_param_value<GLuint>(1); VOGL_NOTE_UNUSED(size);
983 if (cmd == cITCRKeyValueMap)
985 key_value_map &kvm = keyframe_trace_packet.get_key_value_map();
987 dynamic_string cmd_type(kvm.get_string("command_type"));
988 if (cmd_type == "state_snapshot")
990 dynamic_string id(kvm.get_string("binary_id"));
993 vogl_error_printf("%s: Missing binary_id field in glInternalTraceCommandRAD key_valye_map command type: \"%s\"\n", VOGL_FUNCTION_NAME, cmd_type.get_ptr());
997 uint8_vec snapshot_data;
999 timed_scope ts("get_multi_blob_manager().get");
1000 if (!pTrace_reader->get_multi_blob_manager().get(id, snapshot_data) || (snapshot_data.is_empty()))
1002 vogl_error_printf("%s: Failed reading snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
1007 vogl_message_printf("%s: Deserializing state snapshot \"%s\", %u bytes\n", VOGL_FUNCTION_NAME, id.get_ptr(), snapshot_data.size());
1011 timed_scope ts("doc.binary_deserialize");
1012 if (!doc.binary_deserialize(snapshot_data) || (!doc.get_root()))
1014 vogl_error_printf("%s: Failed deserializing JSON snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
1019 pSnapshot = vogl_new(vogl_gl_state_snapshot);
1021 timed_scope ts("pSnapshot->deserialize");
1022 if (!pSnapshot->deserialize(*doc.get_root(), pTrace_reader->get_multi_blob_manager(), &trace_gl_ctypes))
1024 vogl_delete(pSnapshot);
1027 vogl_error_printf("%s: Failed deserializing snapshot blob data \"%s\"!\n", VOGL_FUNCTION_NAME, id.get_ptr());
1031 found_snapshot = true;
1040 } while (!found_snapshot);
1045 bool VoglEditor::open_trace_file(dynamic_string filename)
1047 QCursor origCursor = this->cursor();
1048 this->setCursor(Qt::WaitCursor);
1050 vogl_loose_file_blob_manager file_blob_manager;
1051 dynamic_string keyframe_trace_path(file_utils::get_pathname(filename.get_ptr()));
1052 file_blob_manager.init(cBMFReadable, keyframe_trace_path.get_ptr());
1054 dynamic_string actual_keyframe_filename;
1056 vogleditor_output_message("*********************");
1057 vogleditor_output_message("Opening trace file...");
1058 vogleditor_output_message(filename.c_str());
1060 vogl_trace_file_reader* tmpReader = vogl_open_trace_file(filename, actual_keyframe_filename, NULL);
1062 if (tmpReader == NULL)
1064 vogleditor_output_error("Unable to open trace file.");
1065 this->setCursor(origCursor);
1070 vogleditor_output_message("... success!");
1073 if (tmpReader->get_max_frame_index() > g_settings.trim_large_trace_prompt_size())
1075 int ret = QMessageBox::warning(this, tr(g_PROJECT_NAME.toStdString().c_str()), tr("The loaded trace file has many frames and debugging may be difficult.\nWould you like to trim the trace?"),
1076 QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
1078 if (ret == QMessageBox::Yes)
1080 if (trim_trace_file(filename.c_str(), static_cast<uint>(tmpReader->get_max_frame_index()), g_settings.trim_large_trace_prompt_size()))
1082 // user decided to open the new trim file, and the UI should already be updated
1083 // clean up here and return
1084 vogl_delete(tmpReader);
1085 this->setCursor(origCursor);
1090 // either there was an error, or the user decided NOT to open the trim file,
1091 // so continue to load the original file
1092 vogleditor_output_warning("Large trace files may be difficult to debug.");
1097 // now that we know the new trace file can be opened,
1098 // close the old one, and update the trace reader
1100 m_pTraceReader = tmpReader;
1102 vogl_ctypes trace_ctypes;
1103 trace_ctypes.init(m_pTraceReader->get_sof_packet().m_pointer_sizes);
1105 m_pApiCallTreeModel = new vogleditor_QApiCallTreeModel(m_pTraceReader);
1106 ui->treeView->setModel(m_pApiCallTreeModel);
1108 if (ui->treeView->selectionModel() != NULL)
1110 connect(ui->treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(slot_treeView_currentChanged(const QModelIndex &, const QModelIndex &)));
1113 if (m_pApiCallTreeModel->hasChildren())
1115 ui->treeView->setExpanded(m_pApiCallTreeModel->index(0,0), true);
1116 ui->treeView->setCurrentIndex(m_pApiCallTreeModel->index(0,0));
1119 int flagsColumnWidth = 30;
1120 ui->treeView->header()->setMinimumSectionSize(flagsColumnWidth);
1121 ui->treeView->header()->moveSection(VOGL_ACTC_FLAGS, 0);
1122 ui->treeView->setColumnWidth(VOGL_ACTC_FLAGS, flagsColumnWidth);
1124 int width = ui->treeView->width() - flagsColumnWidth - 30; // subtract a little extra for the scrollbar width
1125 ui->treeView->setColumnWidth(VOGL_ACTC_APICALL, width * 0.7);
1126 ui->treeView->setColumnWidth(VOGL_ACTC_INDEX, width * 0.15);
1127 ui->treeView->setColumnWidth(VOGL_ACTC_DURATION, width * 0.15);
1129 ui->searchTextBox->setEnabled(true);
1130 ui->searchPrevButton->setEnabled(true);
1131 ui->searchNextButton->setEnabled(true);
1133 ui->action_Close->setEnabled(true);
1134 ui->actionSave_Session->setEnabled(true);
1135 ui->actionExport_API_Calls->setEnabled(true);
1137 ui->prevSnapshotButton->setEnabled(true);
1138 ui->nextSnapshotButton->setEnabled(true);
1139 ui->prevDrawcallButton->setEnabled(true);
1140 ui->nextDrawcallButton->setEnabled(true);
1142 m_backtraceToJsonMap.clear();
1143 m_backtraceDoc.clear();
1145 // Extract backtrace map and machine info from trace archive
1146 if (m_pTraceReader->get_archive_blob_manager().is_initialized())
1149 uint8_vec backtrace_data;
1150 bool bBacktraceVisible = false;
1151 if (m_pTraceReader->get_archive_blob_manager().does_exist(VOGL_TRACE_ARCHIVE_BACKTRACE_MAP_ADDRS_FILENAME))
1153 //$ TODO mikesart: read MAP_SYMS data here when symbols have been resolved.
1154 if (m_pTraceReader->get_archive_blob_manager().get(VOGL_TRACE_ARCHIVE_BACKTRACE_MAP_ADDRS_FILENAME, backtrace_data))
1156 json_node* pRoot = m_backtraceDoc.get_root();
1157 if (m_backtraceDoc.deserialize((const char*)backtrace_data.get_ptr(), backtrace_data.size()))
1159 bBacktraceVisible = pRoot->size() > 0;
1160 for (uint i = 0; i < pRoot->size(); i++)
1162 json_node* pChild = pRoot->get_child(i);
1164 VOGL_ASSERT("Backtrace node does not have an 'index' child" && pChild != NULL && pChild->get_value_as_uint32("index", index));
1165 if (pChild != NULL && pChild->get_value_as_uint32("index", index))
1167 m_backtraceToJsonMap.insert(index, pChild);
1174 if (bBacktraceVisible)
1176 if (ui->bottomTabWidget->indexOf(ui->callStackTab) == -1)
1179 ui->bottomTabWidget->insertTab(1, ui->callStackTab, "Call Stack");
1180 VOGLEDITOR_ENABLE_BOTTOM_TAB(ui->callStackTab);
1184 VOGLEDITOR_ENABLE_BOTTOM_TAB(ui->callStackTab);
1189 ui->bottomTabWidget->removeTab(ui->bottomTabWidget->indexOf(ui->callStackTab));
1193 displayMachineInfo();
1196 m_openFilename = filename.c_str();
1198 setWindowTitle(m_openFilename + " - " + g_PROJECT_NAME);
1200 ui->tabWidget->setCurrentWidget(ui->framebufferTab);
1203 m_pPlayButton->setEnabled(true);
1204 m_pTrimButton->setEnabled(true);
1207 m_pTimelineModel = new vogleditor_apiCallTimelineModel(m_pApiCallTreeModel->root());
1208 m_timeline->setModel(m_pTimelineModel);
1209 m_timeline->repaint();
1211 this->setCursor(origCursor);
1215 void VoglEditor::displayMachineInfoHelper(QString prefix, const QString& sectionKeyStr, const vogl::json_value& value, QString& rMachineInfoStr)
1217 if (value.is_array())
1219 const json_node* pNode = value.get_node_ptr();
1220 for (uint element = 0; element < pNode->size(); element++)
1222 dynamic_string elementStr = pNode->get_value(element).as_string();
1224 elementStr = elementStr.replace("\n", "\n\t");
1226 rMachineInfoStr += "\t";
1227 rMachineInfoStr += elementStr.get_ptr();
1228 rMachineInfoStr += "\n";
1231 rMachineInfoStr += "\n";
1233 else if (value.is_node())
1235 // Check if this is the modoule list.
1236 bool is_module_list = (sectionKeyStr == "module_list");
1237 const json_node* pNode = value.get_node_ptr();
1239 for (uint i = 0; i < pNode->size(); i++)
1241 dynamic_string key = pNode->get_key(i);
1242 const json_value &value2 = pNode->get_value(i);
1244 rMachineInfoStr += prefix;
1245 // If it's the module list, then the key is the filename and we want to display that last.
1246 if (!is_module_list)
1247 rMachineInfoStr += key.c_str();
1249 if (value2.is_array())
1251 const json_node* pNode2 = value2.get_node_ptr();
1253 // If this it module_list, then we get these items: base address, address size, uuid
1254 // Check in btrace_get_machine_info() to see what's written there.
1255 for (uint element = 0; element < pNode2->size(); element++)
1257 const json_value &json_val = pNode2->get_value(element);
1259 if (json_val.is_string())
1261 dynamic_string str = pNode2->get_value(element).as_string();
1262 rMachineInfoStr += str.c_str();
1267 buf.format("%" PRIx64, json_val.as_uint64());
1268 rMachineInfoStr += buf.c_str();
1271 rMachineInfoStr += "\t";
1276 rMachineInfoStr += ": ";
1277 rMachineInfoStr += value2.as_string_ptr();
1280 // Display the filename if this is the module_list.
1282 rMachineInfoStr += key.c_str();
1283 rMachineInfoStr += "\n";
1286 rMachineInfoStr += "\n";
1288 else if (value.is_string())
1290 rMachineInfoStr += value.as_string_ptr();
1294 rMachineInfoStr += value.as_string_ptr();
1298 void VoglEditor::displayMachineInfo()
1300 VOGL_ASSERT(m_pTraceReader != NULL);
1301 if (m_pTraceReader == NULL)
1306 bool bMachineInfoVisible = false;
1307 if (m_pTraceReader->get_archive_blob_manager().does_exist(VOGL_TRACE_ARCHIVE_MACHINE_INFO_FILENAME))
1309 uint8_vec machine_info_data;
1310 if (m_pTraceReader->get_archive_blob_manager().get(VOGL_TRACE_ARCHIVE_MACHINE_INFO_FILENAME, machine_info_data))
1312 bMachineInfoVisible = true;
1314 json_node *pRoot = doc.get_root();
1315 if (doc.deserialize((const char*)machine_info_data.get_ptr(), machine_info_data.size()))
1318 for (uint i = 0; i < pRoot->size(); i++)
1320 dynamic_string sectionKeyStr = pRoot->get_key(i);
1321 text += pRoot->get_key(i).c_str();
1324 QString keyStr = sectionKeyStr.c_str();
1325 displayMachineInfoHelper("\t", keyStr, pRoot->get_value(i), text);
1328 ui->machineInfoText->setText(text);
1333 if (bMachineInfoVisible)
1335 if (ui->bottomTabWidget->indexOf(ui->machineInfoTab) == -1)
1338 ui->bottomTabWidget->insertTab(1, ui->machineInfoTab, "Machine Info");
1339 VOGLEDITOR_ENABLE_BOTTOM_TAB(ui->machineInfoTab);
1343 VOGLEDITOR_ENABLE_BOTTOM_TAB(ui->machineInfoTab);
1348 ui->bottomTabWidget->removeTab(ui->bottomTabWidget->indexOf(ui->machineInfoTab));
1352 void VoglEditor::reset_tracefile_ui()
1354 ui->action_Close->setEnabled(false);
1355 ui->actionExport_API_Calls->setEnabled(false);
1356 ui->actionSave_Session->setEnabled(false);
1358 ui->prevSnapshotButton->setEnabled(false);
1359 ui->nextSnapshotButton->setEnabled(false);
1360 ui->prevDrawcallButton->setEnabled(false);
1361 ui->nextDrawcallButton->setEnabled(false);
1362 ui->searchTextBox->clear();
1363 ui->searchTextBox->setEnabled(false);
1364 ui->searchPrevButton->setEnabled(false);
1365 ui->searchNextButton->setEnabled(false);
1367 m_pPlayButton->setEnabled(false);
1368 m_pTrimButton->setEnabled(false);
1370 VOGLEDITOR_DISABLE_BOTTOM_TAB(ui->machineInfoTab);
1371 VOGLEDITOR_DISABLE_BOTTOM_TAB(ui->callStackTab);
1373 reset_snapshot_ui();
1376 void VoglEditor::reset_snapshot_ui()
1378 m_currentSnapshot = NULL;
1380 m_pFramebufferExplorer->clear();
1381 m_pTextureExplorer->clear();
1382 m_pRenderbufferExplorer->clear();
1383 m_pProgramExplorer->clear();
1384 m_pShaderExplorer->clear();
1386 ui->stateTreeView->setModel(NULL);
1388 QWidget* pCurrentTab = ui->tabWidget->currentWidget();
1390 VOGLEDITOR_DISABLE_STATE_TAB(ui->stateTab);
1391 VOGLEDITOR_DISABLE_STATE_TAB(ui->framebufferTab);
1392 VOGLEDITOR_DISABLE_STATE_TAB(ui->programTab);
1393 VOGLEDITOR_DISABLE_STATE_TAB(ui->shaderTab);
1394 VOGLEDITOR_DISABLE_STATE_TAB(ui->textureTab);
1395 VOGLEDITOR_DISABLE_STATE_TAB(ui->renderbufferTab);
1397 ui->tabWidget->setCurrentWidget(pCurrentTab);
1400 /// This helper will most often return a pointer equal to the pCurSnapshot that is passed in, or NULL if the node does not have a snapshot
1401 /// and also has no children. The pMostRecentSnapshot parameter will be updated to point to the desired snapshot.
1402 /// This function does not follow a traditional DFS search because we need to find the desired snapshot then return the one before it.
1403 /// An alternative approach would be to keep a stack of the found snapshots, or even to build up that stack / list as the user
1404 /// generates new snapshots.
1405 vogleditor_gl_state_snapshot* VoglEditor::findMostRecentSnapshot_helper(vogleditor_apiCallTreeItem* pItem, vogleditor_gl_state_snapshot*& pMostRecentSnapshot, const vogleditor_gl_state_snapshot* pCurSnapshot)
1407 // check if this item has a snapshot shot
1408 if (pItem->has_snapshot() && pItem->get_snapshot()->is_valid())
1410 vogleditor_gl_state_snapshot* pTmp = pItem->get_snapshot();
1411 if (pTmp == pCurSnapshot)
1413 // if we've reached the item with the current snapshot, we want to return the previous snapshot.
1418 // update most recent snapshot
1419 pMostRecentSnapshot = pTmp;
1423 for (int i = 0; i < pItem->childCount(); i++)
1425 vogleditor_gl_state_snapshot* pTmp = findMostRecentSnapshot_helper(pItem->child(i), pMostRecentSnapshot, pCurSnapshot);
1428 if (pTmp == pCurSnapshot)
1430 // if we've reached the item with the current snapshot, we want to return the previous snapshot.
1435 // update most recent snapshot
1436 pMostRecentSnapshot = pTmp;
1444 /// This function exists just to simplify the interaction with the helper, so that there no confusion between
1445 /// whether the returned value, or passed in reference parameter should be used as the most recent snapshot.
1446 /// It will either return NULL if there is no recent snapshot (which should only happen for the very first snapshot
1447 /// in a trace), or a pointer to a valid snapshot.
1448 vogleditor_gl_state_snapshot* VoglEditor::findMostRecentSnapshot(vogleditor_apiCallTreeItem* pItem, const vogleditor_gl_state_snapshot* pCurSnapshot)
1450 vogleditor_gl_state_snapshot* pMostRecentSnapshot = NULL;
1451 findMostRecentSnapshot_helper(pItem, pMostRecentSnapshot, pCurSnapshot);
1452 return pMostRecentSnapshot;
1455 void VoglEditor::update_ui_for_snapshot(vogleditor_gl_state_snapshot* pStateSnapshot)
1457 if (pStateSnapshot == NULL)
1459 reset_snapshot_ui();
1463 if (pStateSnapshot->is_valid() == false)
1465 reset_snapshot_ui();
1469 if (m_currentSnapshot == pStateSnapshot)
1471 // no need to update if it is the same snapshot
1475 m_currentSnapshot = pStateSnapshot;
1477 if (ui->stateTreeView->model() != NULL)
1479 if (static_cast<vogleditor_QStateTreeModel*>(ui->stateTreeView->model())->get_snapshot() == m_currentSnapshot)
1481 // displaying the same snapshot, return
1486 QCursor origCursor = this->cursor();
1487 this->setCursor(Qt::WaitCursor);
1490 if (m_pStateTreeModel != NULL)
1492 delete m_pStateTreeModel;
1494 m_pStateTreeModel = new vogleditor_QStateTreeModel(NULL);
1496 vogleditor_gl_state_snapshot* pBaseSnapshot = findMostRecentSnapshot(m_pApiCallTreeModel->root(), m_currentSnapshot);
1497 m_pStateTreeModel->set_diff_base_snapshot(pBaseSnapshot);
1499 m_pStateTreeModel->set_snapshot(pStateSnapshot);
1501 ui->stateTreeView->setModel(m_pStateTreeModel);
1502 ui->stateTreeView->expandToDepth(1);
1503 ui->stateTreeView->setColumnWidth(0, ui->stateTreeView->width() * 0.5);
1505 VOGLEDITOR_ENABLE_STATE_TAB(ui->stateTab);
1507 if (pStateSnapshot->get_contexts().size() > 0)
1509 vogl_trace_ptr_value curContextHandle = pStateSnapshot->get_cur_trace_context();
1510 if (curContextHandle != 0)
1512 vogl_context_snapshot* pContext = pStateSnapshot->get_context(curContextHandle);
1515 vogl_gl_object_state_ptr_vec textureObjects;
1516 pContext->get_all_objects_of_category(cGLSTTexture, textureObjects);
1517 m_pTextureExplorer->set_texture_objects(textureObjects);
1519 GLuint curActiveTextureUnit = pContext->get_general_state().get_value<GLuint>(GL_ACTIVE_TEXTURE);
1520 if (curActiveTextureUnit >= GL_TEXTURE0 && curActiveTextureUnit < (GL_TEXTURE0 + pContext->get_context_info().get_max_texture_image_units()))
1522 GLuint cur2DBinding = pContext->get_general_state().get_value<GLuint>(GL_TEXTURE_2D_BINDING_EXT, curActiveTextureUnit - GL_TEXTURE0);
1523 displayTexture(cur2DBinding, false);
1527 vogl_gl_object_state_ptr_vec renderbufferObjects;
1528 pContext->get_all_objects_of_category(cGLSTRenderbuffer, renderbufferObjects);
1529 m_pRenderbufferExplorer->set_texture_objects(renderbufferObjects);
1530 if (renderbufferObjects.size() > 0) { VOGLEDITOR_ENABLE_STATE_TAB(ui->renderbufferTab); }
1533 vogl_gl_object_state_ptr_vec framebufferObjects;
1534 pContext->get_all_objects_of_category(cGLSTFramebuffer, framebufferObjects);
1535 m_pFramebufferExplorer->set_framebuffer_objects(framebufferObjects, *pContext, pStateSnapshot->get_default_framebuffer());
1536 GLuint64 curDrawFramebuffer = pContext->get_general_state().get_value<GLuint64>(GL_DRAW_FRAMEBUFFER_BINDING);
1537 displayFramebuffer(curDrawFramebuffer, false);
1540 vogl_gl_object_state_ptr_vec programObjects;
1541 pContext->get_all_objects_of_category(cGLSTProgram, programObjects);
1542 m_pProgramExplorer->set_program_objects(programObjects);
1543 GLuint64 curProgram = pContext->get_general_state().get_value<GLuint64>(GL_CURRENT_PROGRAM);
1544 m_pProgramExplorer->set_active_program(curProgram);
1545 if (programObjects.size() > 0) { VOGLEDITOR_ENABLE_STATE_TAB(ui->programTab); }
1548 vogl_gl_object_state_ptr_vec shaderObjects;
1549 pContext->get_all_objects_of_category(cGLSTShader, shaderObjects);
1550 m_pShaderExplorer->set_shader_objects(shaderObjects);
1551 if (curProgram != 0)
1553 for (vogl_gl_object_state_ptr_vec::iterator iter = programObjects.begin(); iter != programObjects.end(); iter++)
1555 if ((*iter)->get_snapshot_handle() == curProgram)
1557 vogl_program_state* pProgramState = static_cast<vogl_program_state*>(*iter);
1558 if (pProgramState->get_attached_shaders().size() > 0)
1560 uint curShader = pProgramState->get_attached_shaders()[0];
1561 m_pShaderExplorer->set_active_shader(curShader);
1567 if (shaderObjects.size() > 0) { VOGLEDITOR_ENABLE_STATE_TAB(ui->shaderTab); }
1571 this->setCursor(origCursor);
1574 void VoglEditor::on_stateTreeView_clicked(const QModelIndex &index)
1576 vogleditor_stateTreeItem* pStateItem = static_cast<vogleditor_stateTreeItem*>(index.internalPointer());
1577 if (pStateItem == NULL)
1582 switch(pStateItem->getStateType())
1584 case vogleditor_stateTreeItem::cTEXTURE:
1586 vogleditor_stateTreeTextureItem* pTextureItem = static_cast<vogleditor_stateTreeTextureItem*>(pStateItem);
1587 if (pTextureItem == NULL)
1592 displayTexture(pTextureItem->get_texture_state()->get_snapshot_handle(), true);
1596 case vogleditor_stateTreeItem::cPROGRAM:
1598 vogleditor_stateTreeProgramItem* pProgramItem = static_cast<vogleditor_stateTreeProgramItem*>(pStateItem);
1599 if (pProgramItem == NULL)
1604 displayProgram(pProgramItem->get_current_state()->get_snapshot_handle(), true);
1608 case vogleditor_stateTreeItem::cSHADER:
1610 vogleditor_stateTreeShaderItem* pShaderItem = static_cast<vogleditor_stateTreeShaderItem*>(pStateItem);
1611 if (pShaderItem == NULL)
1616 displayShader(pShaderItem->get_current_state()->get_snapshot_handle(), true);
1620 case vogleditor_stateTreeItem::cFRAMEBUFFER:
1622 vogleditor_stateTreeFramebufferItem* pFramebufferItem = static_cast<vogleditor_stateTreeFramebufferItem*>(pStateItem);
1623 if (pFramebufferItem == NULL)
1628 displayFramebuffer(pFramebufferItem->get_framebuffer_state()->get_snapshot_handle(), true);
1632 case vogleditor_stateTreeItem::cDEFAULT:
1639 bool VoglEditor::displayShader(GLuint64 shaderHandle, bool bBringTabToFront)
1641 bool bDisplayed = false;
1642 if (m_pShaderExplorer->set_active_shader(shaderHandle))
1644 if (bBringTabToFront)
1646 ui->tabWidget->setCurrentWidget(ui->shaderTab);
1653 void VoglEditor::displayProgram(GLuint64 programHandle, bool bBringTabToFront)
1655 if (m_pProgramExplorer->set_active_program(programHandle))
1657 if (bBringTabToFront)
1659 ui->tabWidget->setCurrentWidget(ui->programTab);
1664 void VoglEditor::displayFramebuffer(GLuint64 framebufferHandle, bool bBringTabToFront)
1666 bool bDisplayedFBO = m_pFramebufferExplorer->set_active_framebuffer(framebufferHandle);
1670 VOGLEDITOR_ENABLE_STATE_TAB(ui->framebufferTab);
1671 if (bBringTabToFront)
1673 ui->tabWidget->setCurrentWidget(ui->framebufferTab);
1678 bool VoglEditor::displayTexture(GLuint64 textureHandle, bool bBringTabToFront)
1680 bool bDisplayedTexture = m_pTextureExplorer->set_active_texture(textureHandle);
1682 if (bDisplayedTexture)
1684 VOGLEDITOR_ENABLE_STATE_TAB(ui->textureTab);
1685 if (bBringTabToFront)
1687 ui->tabWidget->setCurrentWidget(ui->textureTab);
1691 return bDisplayedTexture;
1694 void VoglEditor::slot_treeView_currentChanged(const QModelIndex & current, const QModelIndex & previous)
1696 VOGL_NOTE_UNUSED(previous);
1697 onApiCallSelected(current, false);
1700 void VoglEditor::on_treeView_clicked(const QModelIndex &index)
1702 onApiCallSelected(index, true);
1705 void VoglEditor::onApiCallSelected(const QModelIndex &index, bool bAllowStateSnapshot)
1707 vogleditor_apiCallTreeItem* pCallTreeItem = static_cast<vogleditor_apiCallTreeItem*>(index.internalPointer());
1708 if (pCallTreeItem == NULL)
1713 vogleditor_frameItem* pFrameItem = pCallTreeItem->frameItem();
1714 vogleditor_apiCallItem* pApiCallItem = pCallTreeItem->apiCallItem();
1716 if (bAllowStateSnapshot && pCallTreeItem == m_pCurrentCallTreeItem)
1718 // we can only get snapshots for specific API calls
1719 if (pApiCallItem != NULL && pApiCallItem->needs_snapshot())
1721 // get the snapshot after the current api call
1722 vogleditor_gl_state_snapshot* pNewSnapshot = NULL;
1723 QCursor origCursor = cursor();
1724 setCursor(Qt::WaitCursor);
1725 m_traceReplayer.replay(m_pTraceReader, m_pApiCallTreeModel->root(), &pNewSnapshot, pApiCallItem->globalCallIndex(), false);
1726 setCursor(origCursor);
1727 pCallTreeItem->set_snapshot(pNewSnapshot);
1731 update_ui_for_snapshot(pCallTreeItem->get_snapshot());
1733 if (pApiCallItem != NULL && m_pCurrentCallTreeItem != pCallTreeItem)
1735 if (m_backtraceToJsonMap.size() > 0)
1738 json_node* pBacktraceNode = m_backtraceToJsonMap[(uint)pApiCallItem->backtraceHashIndex()];
1739 if (pBacktraceNode != NULL)
1741 json_node* pAddrs = pBacktraceNode->find_child_array("addrs");
1742 json_node* pSyms = pBacktraceNode->find_child_array("syms");
1744 for (uint i = 0; i < pAddrs->size(); i++)
1746 tmp += pAddrs->get_value(i).as_string_ptr();
1750 tmp += pSyms->get_value(i).as_string_ptr();
1755 ui->backtraceText->setText(tmp);
1759 if (pApiCallItem != NULL)
1761 m_timeline->setCurrentApiCall(pApiCallItem->globalCallIndex());
1764 if (pFrameItem != NULL)
1766 m_timeline->setCurrentFrame(pFrameItem->frameNumber());
1769 m_timeline->repaint();
1771 m_pCurrentCallTreeItem = pCallTreeItem;
1774 void VoglEditor::selectApicallModelIndex(QModelIndex index, bool scrollTo, bool select)
1776 // make sure the index is visible
1777 QModelIndex parentIndex = index.parent();
1778 while (parentIndex.isValid())
1780 if (ui->treeView->isExpanded(parentIndex) == false)
1782 ui->treeView->expand(parentIndex);
1784 parentIndex = parentIndex.parent();
1787 // scroll to the index
1790 ui->treeView->scrollTo(index);
1796 ui->treeView->setCurrentIndex(index);
1800 void VoglEditor::on_searchTextBox_textChanged(const QString &searchText)
1802 QPalette palette(ui->searchTextBox->palette());
1803 palette.setColor(QPalette::Base, m_searchTextboxBackgroundColor);
1804 ui->searchTextBox->setPalette(palette);
1806 if (m_pApiCallTreeModel != NULL)
1808 m_pApiCallTreeModel->set_highlight_search_string(searchText);
1811 // need to briefly give the treeview focus so that it properly redraws and highlights the matching rows
1812 // then return focus to the search textbox so that typed keys are not lost
1813 ui->treeView->setFocus();
1814 ui->searchTextBox->setFocus();
1817 void VoglEditor::on_searchNextButton_clicked()
1819 if (m_pApiCallTreeModel != NULL)
1821 QModelIndex index = m_pApiCallTreeModel->find_next_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
1822 if (index.isValid())
1824 selectApicallModelIndex(index, true, true);
1825 ui->treeView->setFocus();
1830 void VoglEditor::on_searchPrevButton_clicked()
1832 if (m_pApiCallTreeModel != NULL)
1834 QModelIndex index = m_pApiCallTreeModel->find_prev_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
1835 if (index.isValid())
1837 selectApicallModelIndex(index, true, true);
1838 ui->treeView->setFocus();
1843 void VoglEditor::on_prevSnapshotButton_clicked()
1845 if (m_pApiCallTreeModel != NULL)
1847 vogleditor_apiCallTreeItem* pPrevItemWithSnapshot = m_pApiCallTreeModel->find_prev_snapshot(m_pCurrentCallTreeItem);
1848 if (pPrevItemWithSnapshot != NULL)
1850 selectApicallModelIndex(m_pApiCallTreeModel->indexOf(pPrevItemWithSnapshot), true, true);
1851 ui->treeView->setFocus();
1856 void VoglEditor::on_nextSnapshotButton_clicked()
1858 if (m_pApiCallTreeModel != NULL)
1860 vogleditor_apiCallTreeItem* pNextItemWithSnapshot = m_pApiCallTreeModel->find_next_snapshot(m_pCurrentCallTreeItem);
1861 if (pNextItemWithSnapshot != NULL)
1863 selectApicallModelIndex(m_pApiCallTreeModel->indexOf(pNextItemWithSnapshot), true, true);
1864 ui->treeView->setFocus();
1869 void VoglEditor::on_prevDrawcallButton_clicked()
1871 if (m_pApiCallTreeModel != NULL)
1873 vogleditor_apiCallTreeItem* pPrevItem = m_pApiCallTreeModel->find_prev_drawcall(m_pCurrentCallTreeItem);
1874 if (pPrevItem != NULL)
1876 selectApicallModelIndex(m_pApiCallTreeModel->indexOf(pPrevItem), true, true);
1877 ui->treeView->setFocus();
1882 void VoglEditor::on_nextDrawcallButton_clicked()
1884 if (m_pApiCallTreeModel != NULL)
1886 vogleditor_apiCallTreeItem* pNextItem = m_pApiCallTreeModel->find_next_drawcall(m_pCurrentCallTreeItem);
1887 if (pNextItem != NULL)
1889 selectApicallModelIndex(m_pApiCallTreeModel->indexOf(pNextItem), true, true);
1890 ui->treeView->setFocus();
1895 void VoglEditor::slot_program_edited(vogl_program_state* pNewProgramState)
1897 VOGL_NOTE_UNUSED(pNewProgramState);
1899 m_currentSnapshot->set_edited(true);
1901 // update all the snapshot flags
1902 bool bFoundEditedSnapshot = false;
1903 recursive_update_snapshot_flags(m_pApiCallTreeModel->root(), bFoundEditedSnapshot);
1905 // give the tree view focus so that it redraws. This is something of a hack, we don't really want to be changing around which control has focus,
1906 // but right now I don't see it being a major issue. It may be an issue later on depending on how we implement more state editing (ie, if arrow
1907 // keys are used to cycle through options in a drop-down, and the tree view gets focus, the arrow keys would then start changing the selected
1908 // API call instead of cycling through state options).
1909 ui->treeView->setFocus();
1912 // if an edited snapshot has already been found, mark the node (and all children) as dirty.
1913 void VoglEditor::recursive_update_snapshot_flags(vogleditor_apiCallTreeItem* pItem, bool& bFoundEditedSnapshot)
1915 // check if this item has a snapshot shot
1916 if (pItem->has_snapshot())
1918 if (!bFoundEditedSnapshot)
1920 if (pItem->get_snapshot()->is_edited())
1922 bFoundEditedSnapshot = true;
1926 pItem->get_snapshot()->set_outdated(false);
1931 pItem->get_snapshot()->set_outdated(true);
1935 for (int i = 0; i < pItem->childCount(); i++)
1937 recursive_update_snapshot_flags(pItem->child(i), bFoundEditedSnapshot);
1941 #undef VOGLEDITOR_DISABLE_STATE_TAB
1942 #undef VOGLEDITOR_ENABLE_STATE_TAB
1944 #undef VOGLEDITOR_DISABLE_BOTTOM_TAB
1945 #undef VOGLEDITOR_ENABLE_BOTTOM_TAB
1947 void VoglEditor::on_actionSave_Session_triggered()
1949 QString baseName = m_openFilename;
1951 int lastIndex = baseName.lastIndexOf('.');
1952 if (lastIndex != -1)
1954 baseName = baseName.remove(lastIndex, baseName.size() - lastIndex);
1957 QString suggestedName = baseName + "-vogleditor.json";
1959 QString sessionFilename = QFileDialog::getSaveFileName(this, tr("Save Debug Session"), suggestedName, tr("JSON (*.json)"));
1961 if (!save_session_to_disk(sessionFilename))
1963 vogleditor_output_error("Failed to save session.");
1967 void VoglEditor::on_actionOpen_Session_triggered()
1969 QString sessionFilename = QFileDialog::getOpenFileName(this, tr("Load Debug Session"), QString(), tr("JSON (*.json)"));
1971 QCursor origCursor = this->cursor();
1972 setCursor(Qt::WaitCursor);
1974 if (!load_session_from_disk(sessionFilename))
1976 vogleditor_output_error("Failed to load session.");
1979 setCursor(origCursor);
1982 void VoglEditor::on_searchTextBox_returnPressed()
1984 if (m_pApiCallTreeModel != NULL)
1986 QModelIndex index = m_pApiCallTreeModel->find_next_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text());
1987 if (index.isValid())
1989 // a valid item was found, scroll to it and select it
1990 selectApicallModelIndex(index, true, true);
1994 // no items were found, so set the textbox background to red (it will get cleared to the original color if the user edits the search text)
1995 QPalette palette(ui->searchTextBox->palette());
1996 palette.setColor(QPalette::Base, Qt::red);
1997 ui->searchTextBox->setPalette(palette);
2002 void VoglEditor::slot_readReplayStandardOutput()
2004 m_pVoglReplayProcess->setReadChannel(QProcess::StandardOutput);
2005 while (m_pVoglReplayProcess->canReadLine())
2007 QByteArray output = m_pVoglReplayProcess->readLine();
2008 if (output.endsWith("\n"))
2010 output.remove(output.size() - 1, 1);
2012 vogleditor_output_message(output.constData());
2016 void VoglEditor::slot_readReplayStandardError()
2018 m_pVoglReplayProcess->setReadChannel(QProcess::StandardError);
2019 while (m_pVoglReplayProcess->canReadLine())
2021 QByteArray output = m_pVoglReplayProcess->readLine();
2022 if (output.endsWith("\n"))
2024 output.remove(output.size() - 1, 1);
2026 vogleditor_output_error(output.constData());