X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=src%2Fvogleditor%2Fvogleditor.cpp;h=7eb513ef503e4220a3c4c65bbbe4d5c2891ff24c;hb=1e67a55d82c08d8c2ce1ad4303d01607fca61e53;hp=e67967e63322f1c4a8f1be0d5c6c737b1e639281;hpb=efdbc27f2074c559c8bbd4c118c45fda53e7aa7a;p=vogl diff --git a/src/vogleditor/vogleditor.cpp b/src/vogleditor/vogleditor.cpp index e67967e..7eb513e 100644 --- a/src/vogleditor/vogleditor.cpp +++ b/src/vogleditor/vogleditor.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -45,12 +46,15 @@ #include "vogl_texture_format.h" #include "vogl_trace_file_reader.h" +#include "vogl_trace_file_writer.h" #include "vogleditor_qstatetreemodel.h" +#include "vogleditor_settings.h" #include "vogleditor_statetreetextureitem.h" #include "vogleditor_statetreeprogramitem.h" #include "vogleditor_statetreeshaderitem.h" #include "vogleditor_statetreeframebufferitem.h" #include "vogleditor_qtextureexplorer.h" +#include "vogleditor_qtrimdialog.h" #define VOGLEDITOR_DISABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), false); #define VOGLEDITOR_ENABLE_TAB(tab) ui->tabWidget->setTabEnabled(ui->tabWidget->indexOf(tab), true); @@ -60,6 +64,8 @@ //---------------------------------------------------------------------------------------------------------------------- static void *g_actual_libgl_module_handle; static QString g_PROJECT_NAME = "Vogl Editor"; +static vogleditor_settings g_settings; +static const char* g_SETTINGS_FILE = "./vogleditor_settings.json"; //---------------------------------------------------------------------------------------------------------------------- // vogl_get_proc_address_helper @@ -104,21 +110,27 @@ static bool load_gl() VoglEditor::VoglEditor(QWidget *parent) : QMainWindow(parent), ui(new Ui::VoglEditor), - m_statusLabel(NULL), - m_framebufferExplorer(NULL), - m_textureExplorer(NULL), - m_renderbufferExplorer(NULL), - m_programExplorer(NULL), - m_shaderExplorer(NULL), + m_pStatusLabel(NULL), + m_pFramebufferExplorer(NULL), + m_pTextureExplorer(NULL), + m_pRenderbufferExplorer(NULL), + m_pProgramExplorer(NULL), + m_pShaderExplorer(NULL), m_timeline(NULL), + m_pFramebufferTab_layout(NULL), + m_pTextureTab_layout(NULL), + m_pRenderbufferTab_layout(NULL), + m_pProgramTab_layout(NULL), + m_pShaderTab_layout(NULL), m_currentSnapshot(NULL), m_pCurrentCallTreeItem(NULL), + m_pVoglReplayProcess(new QProcess()), m_pPlayButton(NULL), - m_pPauseButton(NULL), m_pTrimButton(NULL), - m_pStopButton(NULL), m_pTraceReader(NULL), - m_pApicallTreeModel(NULL) + m_pTimelineModel(NULL), + m_pApiCallTreeModel(NULL), + m_pStateTreeModel(NULL) { ui->setupUi(this); @@ -127,39 +139,51 @@ VoglEditor::VoglEditor(QWidget *parent) : vogl_init_actual_gl_entrypoints(vogl_get_proc_address_helper); } - m_statusLabel = new QLabel(ui->statusBar); - m_statusLabel->setBaseSize(150, 12); - ui->statusBar->addWidget(m_statusLabel, 1); + // load the settings file. This will only succeed if the file already exists + g_settings.load(g_SETTINGS_FILE); + + // always save/resave the file wiill either be created or so that new settings will be added + g_settings.save(g_SETTINGS_FILE); + + this->move(g_settings.window_position_left(), g_settings.window_position_top()); + this->resize(g_settings.window_size_width(), g_settings.window_size_height()); + + m_pStatusLabel = new QLabel(ui->statusBar); + m_pStatusLabel->setBaseSize(150, 12); + ui->statusBar->addWidget(m_pStatusLabel, 1); + + // cache the original background color of the search text box + m_searchTextboxBackgroundColor = ui->searchTextBox->palette().base().color(); // setup framebuffer tab - QGridLayout* framebufferTab_layout = new QGridLayout; - m_framebufferExplorer = new vogleditor_QFramebufferExplorer(ui->framebufferTab); - framebufferTab_layout->addWidget(m_framebufferExplorer, 0, 0); - ui->framebufferTab->setLayout(framebufferTab_layout); + m_pFramebufferTab_layout = new QGridLayout(); + m_pFramebufferExplorer = new vogleditor_QFramebufferExplorer(ui->framebufferTab); + m_pFramebufferTab_layout->addWidget(m_pFramebufferExplorer, 0, 0); + ui->framebufferTab->setLayout(m_pFramebufferTab_layout); // setup texture tab - QGridLayout* textureTab_layout = new QGridLayout; - m_textureExplorer = new vogleditor_QTextureExplorer(ui->textureTab); - textureTab_layout->addWidget(m_textureExplorer, 0, 0); - ui->textureTab->setLayout(textureTab_layout); + m_pTextureTab_layout = new QGridLayout(); + m_pTextureExplorer = new vogleditor_QTextureExplorer(ui->textureTab); + m_pTextureTab_layout->addWidget(m_pTextureExplorer, 0, 0); + ui->textureTab->setLayout(m_pTextureTab_layout); // setup renderbuffer tab - QGridLayout* rbTab_layout = new QGridLayout; - m_renderbufferExplorer = new vogleditor_QTextureExplorer(ui->renderbufferTab); - rbTab_layout->addWidget(m_renderbufferExplorer, 0, 0); - ui->renderbufferTab->setLayout(rbTab_layout); + m_pRenderbufferTab_layout = new QGridLayout(); + m_pRenderbufferExplorer = new vogleditor_QTextureExplorer(ui->renderbufferTab); + m_pRenderbufferTab_layout->addWidget(m_pRenderbufferExplorer, 0, 0); + ui->renderbufferTab->setLayout(m_pRenderbufferTab_layout); // setup program tab - QGridLayout* programTab_layout = new QGridLayout; - m_programExplorer = new vogleditor_QProgramExplorer(ui->programTab); - programTab_layout->addWidget(m_programExplorer, 0, 0); - ui->programTab->setLayout(programTab_layout); + m_pProgramTab_layout = new QGridLayout(); + m_pProgramExplorer = new vogleditor_QProgramExplorer(ui->programTab); + m_pProgramTab_layout->addWidget(m_pProgramExplorer, 0, 0); + ui->programTab->setLayout(m_pProgramTab_layout); // setup shader tab - QGridLayout* shaderTab_layout = new QGridLayout; - m_shaderExplorer = new vogleditor_QShaderExplorer(ui->shaderTab); - shaderTab_layout->addWidget(m_shaderExplorer, 0, 0); - ui->shaderTab->setLayout(shaderTab_layout); + m_pShaderTab_layout = new QGridLayout(); + m_pShaderExplorer = new vogleditor_QShaderExplorer(ui->shaderTab); + m_pShaderTab_layout->addWidget(m_pShaderExplorer, 0, 0); + ui->shaderTab->setLayout(m_pShaderTab_layout); // setup timeline m_timeline = new vogleditor_QTimelineView(); @@ -169,69 +193,125 @@ VoglEditor::VoglEditor(QWidget *parent) : // add buttons to toolbar m_pPlayButton = new QToolButton(ui->mainToolBar); - m_pPlayButton->setText("Play trace"); + m_pPlayButton->setText("Play Trace"); m_pPlayButton->setEnabled(false); - m_pPauseButton = new QToolButton(ui->mainToolBar); - m_pPauseButton->setText("Pause"); - m_pPauseButton->setEnabled(false); - m_pTrimButton = new QToolButton(ui->mainToolBar); - m_pTrimButton->setText("Trim"); + m_pTrimButton->setText("Trim Trace"); m_pTrimButton->setEnabled(false); - m_pStopButton = new QToolButton(ui->mainToolBar); - m_pStopButton->setText("Stop"); - m_pStopButton->setEnabled(false); - - // Temporarily hide the other buttons (until asyncronous playback is supported) - m_pPauseButton->setVisible(false); - m_pTrimButton->setVisible(false); - m_pStopButton->setVisible(false); - ui->mainToolBar->addWidget(m_pPlayButton); - ui->mainToolBar->addWidget(m_pPauseButton); ui->mainToolBar->addWidget(m_pTrimButton); - ui->mainToolBar->addWidget(m_pStopButton); connect(m_pPlayButton, SIGNAL(clicked()), this, SLOT(playCurrentTraceFile())); - connect(m_pPauseButton, SIGNAL(clicked()), this, SLOT(pauseCurrentTraceFile())); connect(m_pTrimButton, SIGNAL(clicked()), this, SLOT(trimCurrentTraceFile())); - connect(m_pStopButton, SIGNAL(clicked()), this, SLOT(stopCurrentTraceFile())); - connect(m_programExplorer, SIGNAL(program_edited(vogl_program_state*)), this, SLOT(on_program_edited(vogl_program_state*))); + connect(m_pProgramExplorer, SIGNAL(program_edited(vogl_program_state*)), this, SLOT(on_program_edited(vogl_program_state*))); reset_tracefile_ui(); } VoglEditor::~VoglEditor() { - close_trace_file(); - delete ui; + // update any settings and save the settings file + g_settings.set_window_position_left(this->x()); + g_settings.set_window_position_top(this->y()); + g_settings.set_window_size_width(this->width()); + g_settings.set_window_size_height(this->height()); + g_settings.save(g_SETTINGS_FILE); - if (m_textureExplorer != NULL) - { - delete m_textureExplorer; - m_textureExplorer = NULL; - } + close_trace_file(); + delete ui; - if (m_renderbufferExplorer != NULL) - { - delete m_renderbufferExplorer; - m_renderbufferExplorer = NULL; - } + if (m_pStatusLabel != NULL) + { + delete m_pStatusLabel; + m_pStatusLabel = NULL; + } - if (m_programExplorer != NULL) - { - delete m_programExplorer; - m_programExplorer = NULL; - } + if (m_pFramebufferExplorer != NULL) + { + delete m_pFramebufferExplorer; + m_pFramebufferExplorer = NULL; + } - if (m_shaderExplorer != NULL) - { - delete m_shaderExplorer; - m_shaderExplorer = NULL; - } + if (m_pTextureExplorer != NULL) + { + delete m_pTextureExplorer; + m_pTextureExplorer = NULL; + } + + if (m_pRenderbufferExplorer != NULL) + { + delete m_pRenderbufferExplorer; + m_pRenderbufferExplorer = NULL; + } + + if (m_pProgramExplorer != NULL) + { + delete m_pProgramExplorer; + m_pProgramExplorer = NULL; + } + + if (m_pShaderExplorer != NULL) + { + delete m_pShaderExplorer; + m_pShaderExplorer = NULL; + } + + if (m_pPlayButton != NULL) + { + delete m_pPlayButton; + m_pPlayButton = NULL; + } + + if (m_pTrimButton != NULL) + { + delete m_pTrimButton; + m_pTrimButton = NULL; + } + + if (m_pFramebufferTab_layout != NULL) + { + delete m_pFramebufferTab_layout; + m_pFramebufferTab_layout = NULL; + } + + if (m_pTextureTab_layout != NULL) + { + delete m_pTextureTab_layout; + m_pTextureTab_layout = NULL; + } + + if (m_pRenderbufferTab_layout != NULL) + { + delete m_pRenderbufferTab_layout; + m_pRenderbufferTab_layout = NULL; + } + + if (m_pProgramTab_layout != NULL) + { + delete m_pProgramTab_layout; + m_pProgramTab_layout = NULL; + } + + if (m_pShaderTab_layout != NULL) + { + delete m_pShaderTab_layout; + m_pShaderTab_layout = NULL; + } + + if (m_pStateTreeModel != NULL) + { + delete m_pStateTreeModel; + m_pStateTreeModel = NULL; + } + + if (m_pVoglReplayProcess != NULL) + { + delete m_pVoglReplayProcess; + m_pVoglReplayProcess = NULL; + } } void VoglEditor::playCurrentTraceFile() @@ -241,71 +321,96 @@ void VoglEditor::playCurrentTraceFile() // update UI m_pPlayButton->setEnabled(false); - m_pPauseButton->setEnabled(true); - m_pTrimButton->setEnabled(true); - m_pStopButton->setEnabled(true); - m_statusLabel->clear(); + m_pTrimButton->setEnabled(false); + m_pStatusLabel->clear(); - if (m_traceReplayer.replay(m_pTraceReader, m_pApicallTreeModel->root(), NULL, 0, true)) + if (m_traceReplayer.replay(m_pTraceReader, m_pApiCallTreeModel->root(), NULL, 0, true)) { // replay was successful m_pPlayButton->setEnabled(true); - m_pPauseButton->setEnabled(false); - m_pTrimButton->setEnabled(false); - m_pStopButton->setEnabled(false); + m_pTrimButton->setEnabled(true); } else { - m_statusLabel->setText("Failed to replay the trace."); + m_pStatusLabel->setText("Failed to replay the trace."); } setCursor(origCursor); } -void VoglEditor::pauseCurrentTraceFile() +void VoglEditor::trimCurrentTraceFile() { - if (m_traceReplayer.pause()) + trim_trace_file(m_openFilename, static_cast(m_pTraceReader->get_max_frame_index()), g_settings.trim_large_trace_prompt_size()); +} + +/// \return True if the new trim file is now open in the editor +/// \return False if there was an error, or the user elected NOT to open the new trim file +bool VoglEditor::trim_trace_file(QString filename, uint maxFrameIndex, uint maxAllowedTrimLen) +{ + // open a dialog to gather parameters for the replayer + vogleditor_QTrimDialog trimDialog(filename, maxFrameIndex, maxAllowedTrimLen, this); + int code = trimDialog.exec(); + + if (code == QDialog::Rejected) { - // update UI - m_pPlayButton->setEnabled(true); - m_pPauseButton->setEnabled(false); - m_pTrimButton->setEnabled(true); - m_pStopButton->setEnabled(true); - m_statusLabel->clear(); + return false; } - else + + QStringList arguments; + arguments << "--trim_frame" << trimDialog.trim_frame() << "--trim_len" << trimDialog.trim_len() << "--trim_file" << trimDialog.trim_file() << filename; + +#ifdef __i386__ + int procRetValue = m_pVoglReplayProcess->execute("./voglreplay32", arguments); +#else + int procRetValue = m_pVoglReplayProcess->execute("./voglreplay64", arguments); +#endif + + bool bCompleted = false; + if (procRetValue == -2) { - m_statusLabel->setText("Failed to pause the trace replay."); + // proc failed to starts + m_pStatusLabel->setText("Error: voglreplay could not be executed."); } -} - -void VoglEditor::trimCurrentTraceFile() -{ - if (m_traceReplayer.trim()) + else if (procRetValue == -1) + { + // proc crashed + m_pStatusLabel->setText("Error: voglreplay aborted unexpectedly."); + } + else if (procRetValue == 0) { - m_statusLabel->clear(); + // success + bCompleted = true; } else { - m_statusLabel->setText("Failed to trim the trace replay."); + // some other return value + bCompleted = false; } -} -void VoglEditor::stopCurrentTraceFile() -{ - if (m_traceReplayer.stop()) + if (bCompleted) { - // update UI - m_pPlayButton->setEnabled(true); - m_pPauseButton->setEnabled(false); - m_pTrimButton->setEnabled(false); - m_pStopButton->setEnabled(false); - m_statusLabel->clear(); + int ret = QMessageBox::warning(this, tr(g_PROJECT_NAME.toStdString().c_str()), tr("Would you like to load the new trimmed trace file?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + + if (ret == QMessageBox::Yes) + { + close_trace_file(); + if (open_trace_file(trimDialog.trim_file().toStdString().c_str())) + { + m_pStatusLabel->clear(); + return true; + } + else + { + QMessageBox::critical(this, tr("Error"), tr("Could not open trace file.")); + } + } } else { - m_statusLabel->setText("Failed to stop the trace replay."); + QMessageBox::critical(this, tr("Error"), tr("Failed to trim the trace file.")); } + return false; } void VoglEditor::on_actionE_xit_triggered() @@ -323,7 +428,7 @@ void VoglEditor::on_action_Open_triggered() filename.set(fileName.toStdString().c_str()); if (open_trace_file(filename) == false) { - QMessageBox::critical(this, tr("Error"), tr("Could not open file")); + QMessageBox::critical(this, tr("Error"), tr("Could not open trace file.")); return; } } @@ -339,6 +444,7 @@ void VoglEditor::close_trace_file() if (m_pTraceReader != NULL) { m_pTraceReader->close(); + vogl_delete(m_pTraceReader); m_pTraceReader = NULL; setWindowTitle(g_PROJECT_NAME); @@ -346,7 +452,6 @@ void VoglEditor::close_trace_file() m_openFilename.clear(); m_backtraceToJsonMap.clear(); m_backtraceDoc.clear(); - m_searchApicallResults.clear(); reset_tracefile_ui(); @@ -361,6 +466,12 @@ void VoglEditor::close_trace_file() delete m_pTimelineModel; m_pTimelineModel = NULL; } + + if (m_pApiCallTreeModel != NULL) + { + delete m_pApiCallTreeModel; + m_pApiCallTreeModel = NULL; + } } } @@ -387,8 +498,7 @@ void VoglEditor::on_actionExport_API_Calls_triggered() } suggestedName += "-ApiCalls.txt"; - QString fileName = QFileDialog::getSaveFileName(this, tr("Export API Calls"), suggestedName, - tr("Text (*.txt)")); + QString fileName = QFileDialog::getSaveFileName(this, tr("Export API Calls"), suggestedName, tr("Text (*.txt)")); if (!fileName.isEmpty()) { @@ -406,6 +516,394 @@ void VoglEditor::on_actionExport_API_Calls_triggered() } } +static const unsigned int VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1 = 1; +static const unsigned int VOGLEDITOR_SESSION_FILE_FORMAT_VERSION = VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1; + +bool VoglEditor::load_session_from_disk(QString sessionFile) +{ + // open the json doc + json_document sessionDoc; + if (!sessionDoc.deserialize_file(sessionFile.toStdString().c_str())) + { + return false; + } + + // look for expected metadata + json_node* pMetadata = sessionDoc.get_root()->find_child_object("metadata"); + if (pMetadata == NULL) + { + return false; + } + + const json_value& rFormatVersion = pMetadata->find_value("session_file_format_version"); + if (!rFormatVersion.is_valid()) + { + return false; + } + + if (rFormatVersion.as_uint32() != VOGLEDITOR_SESSION_FILE_FORMAT_VERSION_1) + { + return false; + } + + // load base trace file + json_node* pBaseTraceFile = sessionDoc.get_root()->find_child_object("base_trace_file"); + if (pBaseTraceFile == NULL) + { + return false; + } + + const json_value& rBaseTraceFilePath = pBaseTraceFile->find_value("rel_path"); + const json_value& rBaseTraceFileUuid = pBaseTraceFile->find_value("uuid"); + + if (!rBaseTraceFilePath.is_valid() || !rBaseTraceFileUuid.is_valid()) + { + return false; + } + + dynamic_string sessionPathName; + dynamic_string sessionFileName; + file_utils::split_path(sessionFile.toStdString().c_str(), sessionPathName, sessionFileName); + + dynamic_string traceFilePath = sessionPathName; + traceFilePath.append(rBaseTraceFilePath.as_string()); + + if (!open_trace_file(traceFilePath)) + { + return false; + } + + // TODO: verify UUID of the loaded trace file + + // load session data if it is available + json_node* pSessionData = sessionDoc.get_root()->find_child_object("session_data"); + if (pSessionData != NULL) + { + const json_value& rSessionPath = pSessionData->find_value("rel_path"); + if (!rSessionPath.is_valid()) + { + return false; + } + + dynamic_string sessionDataPath = sessionPathName; + sessionDataPath.append(rSessionPath.as_string()); + + vogl_loose_file_blob_manager file_blob_manager; + file_blob_manager.init(cBMFReadWrite, sessionDataPath.c_str()); + vogl_blob_manager* pBlob_manager = static_cast(&file_blob_manager); + + // load snapshots + const json_node* pSnapshots = pSessionData->find_child_array("snapshots"); + for (unsigned int i = 0; i < pSnapshots->size(); i++) + { + const json_node* pSnapshotNode = pSnapshots->get_value_as_object(i); + + const json_value& uuid = pSnapshotNode->find_value("uuid"); + const json_value& isValid = pSnapshotNode->find_value("is_valid"); + const json_value& isEdited = pSnapshotNode->find_value("is_edited"); + const json_value& isOutdated = pSnapshotNode->find_value("is_outdated"); + const json_value& frameNumber = pSnapshotNode->find_value("frame_number"); + const json_value& callIndex = pSnapshotNode->find_value("call_index"); + const json_value& path = pSnapshotNode->find_value("rel_path"); + + // make sure expected nodes are valid + if (!isValid.is_valid() || !isEdited.is_valid() || !isOutdated.is_valid()) + { + return false; + } + + vogl_gl_state_snapshot* pSnapshot = NULL; + + if (path.is_valid() && isValid.as_bool() && uuid.is_valid()) + { + dynamic_string snapshotPath = sessionDataPath; + snapshotPath.append(path.as_string()); + + // load the snapshot + json_document snapshotDoc; + if (!snapshotDoc.deserialize_file(snapshotPath.c_str())) + { + return false; + } + + // attempt to verify the snapshot file + json_node* pSnapshotRoot = snapshotDoc.get_root(); + if (pSnapshotRoot == NULL) + { + vogl_warning_printf("Invalid snapshot file at %s.", path.as_string_ptr()); + continue; + } + + const json_value& snapshotUuid = pSnapshotRoot->find_value("uuid"); + if (!snapshotUuid.is_valid()) + { + vogl_warning_printf("Invalid 'uuid' in snapshot file at %s.", path.as_string_ptr()); + continue; + } + + if (snapshotUuid.as_string() != uuid.as_string()) + { + 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()); + continue; + } + + vogl_ctypes trace_gl_ctypes(m_pTraceReader->get_sof_packet().m_pointer_sizes); + pSnapshot = vogl_new(vogl_gl_state_snapshot); + if (!pSnapshot->deserialize(*snapshotDoc.get_root(), *pBlob_manager, &trace_gl_ctypes)) + { + vogl_delete(pSnapshot); + pSnapshot = NULL; + vogl_warning_printf("Unable to deserialize the snapshot with uuid %s.", uuid.as_string_ptr()); + continue; + } + } + + vogleditor_gl_state_snapshot* pContainer = vogl_new(vogleditor_gl_state_snapshot, pSnapshot); + pContainer->set_edited(isEdited.as_bool()); + pContainer->set_outdated(isOutdated.as_bool()); + + if (callIndex.is_valid()) + { + // the snapshot is associated with an api call + vogleditor_apiCallTreeItem* pItem = m_pApiCallTreeModel->find_call_number(callIndex.as_uint64()); + if (pItem != NULL) + { + pItem->set_snapshot(pContainer); + } + else + { + vogl_warning_printf("Unable to find API call index %" PRIu64 " to load the snapshot into.", callIndex.as_uint64()); + if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; } + if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; } + } + } + else if (frameNumber.is_valid()) + { + // the snapshot is associated with a frame. + // frame snapshots have the additional requirement that the snapshot itself MUST exist since + // we only save a frame snapshot if it is the inital frame and it has been edited. + // If we allow NULL snapshots, that we could accidently remove the initial snapshot that was loaded with the trace file. + if (pSnapshot != NULL) + { + vogleditor_apiCallTreeItem* pItem = m_pApiCallTreeModel->find_frame_number(frameNumber.as_uint64()); + if (pItem != NULL) + { + pItem->set_snapshot(pContainer); + } + else + { + vogl_warning_printf("Unable to find frame number %" PRIu64 " to load the snapshot into.", frameNumber.as_uint64()); + if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; } + if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; } + } + } + } + else + { + vogl_warning_printf("Session file contains invalid call or frame number for snapshot with uuid %s", uuid.as_string_ptr()); + if (pSnapshot != NULL) { vogl_delete(pSnapshot); pSnapshot = NULL; } + if (pContainer != NULL) { vogl_delete(pContainer); pContainer = NULL; } + } + } + } + + return true; +} + +/* + * 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. + * Note that not all of this information is currently supported (either by VoglEditor or the save/load functionality). + * + * sample data structure for version 1: +{ + "metadata" : { + "session_file_format_version" : "0x1" <- would need to be updated when organization of existing data is changed + }, + "base_trace_file" : { + "path" : "../traces/trimmed4.bin", + "uuid" : [ 2761638124, 1361789091, 2623121922, 1789156619 ] + }, + "session_data" : { + "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/", + "snapshots" : [ + { + "uuid" : "B346B680801ED2F5144E421DEA5EFDCC", + "is_valid" : true, + "is_edited" : false, + "is_outdated" : false, + "frame_number" : 0 + }, + { + "uuid" : "BC261B884088DBEADF376A03A489F2B9", + "is_valid" : true, + "is_edited" : false, + "is_outdated" : false, + "call_index" : 881069, + "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/snapshot_call_881069.json" + }, + { + "uuid" : "176DE3DEAA437B871FE122C84D5432E3", + "is_valid" : true, + "is_edited" : true, + "is_outdated" : false, + "call_index" : 881075, + "path" : "/home/peterl/voglproj/vogl_build/traces/trimmed4-vogleditor-sessiondata/snapshot_call_881075.json" + }, + { + "is_valid" : false, + "is_edited" : false, + "is_outdated" : true, + "call_index" : 881080 + } + ] + } +} +*/ +bool VoglEditor::save_session_to_disk(QString sessionFile) +{ + dynamic_string sessionPathName; + dynamic_string sessionFileName; + file_utils::split_path(sessionFile.toStdString().c_str(), sessionPathName, sessionFileName); + + // modify the session file name to make a sessiondata folder + QString sessionDataFolder(sessionFileName.c_str()); + int lastIndex = sessionDataFolder.lastIndexOf('.'); + if (lastIndex != -1) + { + sessionDataFolder = sessionDataFolder.remove(lastIndex, sessionDataFolder.size() - lastIndex); + } + sessionDataFolder += "-sessiondata/"; + + dynamic_string sessionDataPath = sessionPathName; + sessionDataPath.append(sessionDataFolder.toStdString().c_str()); + file_utils::create_directories(sessionDataPath, false); + + vogl_loose_file_blob_manager file_blob_manager; + file_blob_manager.init(cBMFReadWrite, sessionDataPath.c_str()); + vogl_blob_manager* pBlob_manager = static_cast(&file_blob_manager); + + QCursor origCursor = this->cursor(); + setCursor(Qt::WaitCursor); + + json_document sessionDoc; + json_node& metadata = sessionDoc.get_root()->add_object("metadata"); + metadata.add_key_value("session_file_format_version", to_hex_string(VOGLEDITOR_SESSION_FILE_FORMAT_VERSION)); + + // find relative path from session file to trace file + QDir relativeAppDir; + QString absoluteTracePath = relativeAppDir.absoluteFilePath(m_openFilename.toStdString().c_str()); + QDir absoluteSessionFileDir(sessionPathName.c_str()); + QString tracePathRelativeToSessionFile = absoluteSessionFileDir.relativeFilePath(absoluteTracePath); + + json_node& baseTraceFile = sessionDoc.get_root()->add_object("base_trace_file"); + baseTraceFile.add_key_value("rel_path", tracePathRelativeToSessionFile.toStdString().c_str()); + json_node &uuid_array = baseTraceFile.add_array("uuid"); + for (uint i = 0; i < VOGL_ARRAY_SIZE(m_pTraceReader->get_sof_packet().m_uuid); i++) + { + uuid_array.add_value(m_pTraceReader->get_sof_packet().m_uuid[i]); + } + + json_node& sessionDataNode = sessionDoc.get_root()->add_object("session_data"); + sessionDataNode.add_key_value("rel_path", sessionDataFolder.toStdString().c_str()); + json_node& snapshotArray = sessionDataNode.add_array("snapshots"); + + vogleditor_apiCallTreeItem* pItem = m_pApiCallTreeModel->find_next_snapshot(NULL); + vogleditor_apiCallTreeItem* pLastItem = NULL; + bool bSavedSuccessfully = true; + while (pItem != pLastItem && pItem != NULL) + { + dynamic_string filename; + + json_node& snapshotNode = snapshotArray.add_object(); + if (pItem->get_snapshot()->get_snapshot() != NULL) + { + dynamic_string strUUID; + snapshotNode.add_key_value("uuid", pItem->get_snapshot()->get_snapshot()->get_uuid().get_string(strUUID)); + } + snapshotNode.add_key_value("is_valid", pItem->get_snapshot()->is_valid()); + snapshotNode.add_key_value("is_edited", pItem->get_snapshot()->is_edited()); + snapshotNode.add_key_value("is_outdated", pItem->get_snapshot()->is_outdated()); + + if (pItem->apiCallItem() != NULL) + { + uint64_t callIndex = pItem->apiCallItem()->globalCallIndex(); + snapshotNode.add_key_value("call_index", callIndex); + if (pItem->get_snapshot()->get_snapshot() != NULL) + { + filename = filename.format("snapshot_call_%" PRIu64 ".json", callIndex); + snapshotNode.add_key_value("rel_path", filename); + dynamic_string filepath = sessionDataPath; + filepath.append(filename); + if (!save_snapshot_to_disk(pItem->get_snapshot()->get_snapshot(), filepath, pBlob_manager)) + { + bSavedSuccessfully = false; + break; + } + } + } + else if (pItem->frameItem() != NULL) + { + // the first frame of a trim will have a snapshot. + // this should only be saved out if the snapshot has been edited + uint64_t frameNumber = pItem->frameItem()->frameNumber(); + snapshotNode.add_key_value("frame_number", frameNumber); + if (pItem->get_snapshot()->is_edited()) + { + filename = filename.format("snapshot_frame_%" PRIu64 ".json", frameNumber); + snapshotNode.add_key_value("rel_path", filename); + dynamic_string filepath = sessionDataPath; + filepath.append(filename); + if (!save_snapshot_to_disk(pItem->get_snapshot()->get_snapshot(), filepath, pBlob_manager)) + { + bSavedSuccessfully = false; + break; + } + } + } + + pLastItem = pItem; + pItem = m_pApiCallTreeModel->find_next_snapshot(pLastItem); + } + + if (bSavedSuccessfully) + { + bSavedSuccessfully = sessionDoc.serialize_to_file(sessionFile.toStdString().c_str()); + } + + setCursor(origCursor); + + return bSavedSuccessfully; +} + +bool VoglEditor::save_snapshot_to_disk(vogl_gl_state_snapshot *pSnapshot, dynamic_string filename, vogl_blob_manager *pBlob_manager) +{ + if (pSnapshot == NULL) + { + return false; + } + + json_document doc; + + vogl_ctypes trace_gl_ctypes(m_pTraceReader->get_sof_packet().m_pointer_sizes); + + if (!pSnapshot->serialize(*doc.get_root(), *pBlob_manager, &trace_gl_ctypes)) + { + vogl_error_printf("Failed serializing state snapshot document!\n"); + return false; + } + else if (!doc.serialize_to_file(filename.get_ptr(), true)) + { + vogl_error_printf("Failed writing state snapshot to file \"%s\"!\n", filename.get_ptr()); + return false; + } + else + { + vogl_printf("Successfully wrote JSON snapshot to file \"%s\"\n", filename.get_ptr()); + } + + return true; +} + //---------------------------------------------------------------------------------------------------------------------- // read_state_snapshot_from_trace //---------------------------------------------------------------------------------------------------------------------- @@ -538,14 +1036,37 @@ bool VoglEditor::open_trace_file(dynamic_string filename) if (tmpReader == NULL) { - m_statusLabel->setText("Failed to open: "); - m_statusLabel->setText(m_statusLabel->text().append(filename.c_str())); + m_pStatusLabel->setText("Failed to open: "); + m_pStatusLabel->setText(m_pStatusLabel->text().append(filename.c_str())); this->setCursor(origCursor); return false; } else { - m_statusLabel->clear(); + m_pStatusLabel->clear(); + } + + if (tmpReader->get_max_frame_index() > g_settings.trim_large_trace_prompt_size()) + { + 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?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); + + if (ret == QMessageBox::Yes) + { + if (trim_trace_file(filename.c_str(), static_cast(tmpReader->get_max_frame_index()), g_settings.trim_large_trace_prompt_size())) + { + // user decided to open the new trim file, and the UI should already be updated + // clean up here and return + vogl_delete(tmpReader); + this->setCursor(origCursor); + return true; + } + else + { + // either there was an error, or the user decided NOT to open the trim file, + // so continue to load the original file + } + } } // now that we know the new trace file can be opened, @@ -553,8 +1074,11 @@ bool VoglEditor::open_trace_file(dynamic_string filename) close_trace_file(); m_pTraceReader = tmpReader; - m_pApicallTreeModel = new vogleditor_QApiCallTreeModel(m_pTraceReader); - ui->treeView->setModel(m_pApicallTreeModel); + vogl_ctypes trace_ctypes; + trace_ctypes.init(m_pTraceReader->get_sof_packet().m_pointer_sizes); + + m_pApiCallTreeModel = new vogleditor_QApiCallTreeModel(m_pTraceReader); + ui->treeView->setModel(m_pApiCallTreeModel); if (ui->treeView->selectionModel() != NULL) { @@ -562,10 +1086,10 @@ bool VoglEditor::open_trace_file(dynamic_string filename) connect(ui->treeView->selectionModel(), SIGNAL(currentChanged(const QModelIndex &, const QModelIndex &)), this, SLOT(on_treeView_currentChanged(const QModelIndex &, const QModelIndex &))); } - if (m_pApicallTreeModel->hasChildren()) + if (m_pApiCallTreeModel->hasChildren()) { - ui->treeView->setExpanded(m_pApicallTreeModel->index(0,0), true); - ui->treeView->setCurrentIndex(m_pApicallTreeModel->index(0,0)); + ui->treeView->setExpanded(m_pApiCallTreeModel->index(0,0), true); + ui->treeView->setCurrentIndex(m_pApiCallTreeModel->index(0,0)); } int flagsColumnWidth = 30; @@ -583,6 +1107,7 @@ bool VoglEditor::open_trace_file(dynamic_string filename) ui->searchNextButton->setEnabled(true); ui->action_Close->setEnabled(true); + ui->actionSave_Session->setEnabled(true); ui->actionExport_API_Calls->setEnabled(true); ui->prevSnapshotButton->setEnabled(true); @@ -650,12 +1175,10 @@ bool VoglEditor::open_trace_file(dynamic_string filename) // update toolbar m_pPlayButton->setEnabled(true); - m_pPauseButton->setEnabled(false); - m_pTrimButton->setEnabled(false); - m_pStopButton->setEnabled(false); + m_pTrimButton->setEnabled(true); // timeline - m_pTimelineModel = new vogleditor_apiCallTimelineModel(m_pApicallTreeModel->root()); + m_pTimelineModel = new vogleditor_apiCallTimelineModel(m_pApiCallTreeModel->root()); m_timeline->setModel(m_pTimelineModel); m_timeline->repaint(); @@ -799,6 +1322,7 @@ void VoglEditor::reset_tracefile_ui() { ui->action_Close->setEnabled(false); ui->actionExport_API_Calls->setEnabled(false); + ui->actionSave_Session->setEnabled(false); ui->prevSnapshotButton->setEnabled(false); ui->nextSnapshotButton->setEnabled(false); @@ -809,11 +1333,9 @@ void VoglEditor::reset_tracefile_ui() ui->searchPrevButton->setEnabled(false); ui->searchNextButton->setEnabled(false); - m_statusLabel->clear(); + m_pStatusLabel->clear(); m_pPlayButton->setEnabled(false); - m_pPauseButton->setEnabled(false); m_pTrimButton->setEnabled(false); - m_pStopButton->setEnabled(false); VOGLEDITOR_DISABLE_TAB(ui->machineInfoTab); @@ -824,11 +1346,11 @@ void VoglEditor::reset_snapshot_ui() { m_currentSnapshot = NULL; - m_framebufferExplorer->clear(); - m_textureExplorer->clear(); - m_renderbufferExplorer->clear(); - m_programExplorer->clear(); - m_shaderExplorer->clear(); + m_pFramebufferExplorer->clear(); + m_pTextureExplorer->clear(); + m_pRenderbufferExplorer->clear(); + m_pProgramExplorer->clear(); + m_pShaderExplorer->clear(); ui->stateTreeView->setModel(NULL); @@ -852,7 +1374,7 @@ void VoglEditor::reset_snapshot_ui() vogleditor_gl_state_snapshot* VoglEditor::findMostRecentSnapshot_helper(vogleditor_apiCallTreeItem* pItem, vogleditor_gl_state_snapshot*& pMostRecentSnapshot, const vogleditor_gl_state_snapshot* pCurSnapshot) { // check if this item has a snapshot shot - if (pItem->has_snapshot()) + if (pItem->has_snapshot() && pItem->get_snapshot()->is_valid()) { vogleditor_gl_state_snapshot* pTmp = pItem->get_snapshot(); if (pTmp == pCurSnapshot) @@ -934,15 +1456,18 @@ void VoglEditor::update_ui_for_snapshot(vogleditor_gl_state_snapshot* pStateSnap this->setCursor(Qt::WaitCursor); // state viewer - vogleditor_QStateTreeModel* pStateModel = new vogleditor_QStateTreeModel(NULL); + if (m_pStateTreeModel != NULL) + { + delete m_pStateTreeModel; + } + m_pStateTreeModel = new vogleditor_QStateTreeModel(NULL); - vogleditor_QApiCallTreeModel* pTreeModel = static_cast(ui->treeView->model()); - vogleditor_gl_state_snapshot* pBaseSnapshot = findMostRecentSnapshot(pTreeModel->root(), m_currentSnapshot); - pStateModel->set_diff_base_snapshot(pBaseSnapshot); + vogleditor_gl_state_snapshot* pBaseSnapshot = findMostRecentSnapshot(m_pApiCallTreeModel->root(), m_currentSnapshot); + m_pStateTreeModel->set_diff_base_snapshot(pBaseSnapshot); - pStateModel->set_snapshot(pStateSnapshot); + m_pStateTreeModel->set_snapshot(pStateSnapshot); - ui->stateTreeView->setModel(pStateModel); + ui->stateTreeView->setModel(m_pStateTreeModel); ui->stateTreeView->expandToDepth(1); ui->stateTreeView->setColumnWidth(0, ui->stateTreeView->width() * 0.5); @@ -958,7 +1483,7 @@ void VoglEditor::update_ui_for_snapshot(vogleditor_gl_state_snapshot* pStateSnap // textures vogl_gl_object_state_ptr_vec textureObjects; pContext->get_all_objects_of_category(cGLSTTexture, textureObjects); - m_textureExplorer->set_texture_objects(textureObjects); + m_pTextureExplorer->set_texture_objects(textureObjects); GLuint curActiveTextureUnit = pContext->get_general_state().get_value(GL_ACTIVE_TEXTURE); if (curActiveTextureUnit >= GL_TEXTURE0 && curActiveTextureUnit < (GL_TEXTURE0 + pContext->get_context_info().get_max_texture_image_units())) @@ -970,28 +1495,28 @@ void VoglEditor::update_ui_for_snapshot(vogleditor_gl_state_snapshot* pStateSnap // renderbuffers vogl_gl_object_state_ptr_vec renderbufferObjects; pContext->get_all_objects_of_category(cGLSTRenderbuffer, renderbufferObjects); - m_renderbufferExplorer->set_texture_objects(renderbufferObjects); + m_pRenderbufferExplorer->set_texture_objects(renderbufferObjects); if (renderbufferObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->renderbufferTab); } // framebuffer vogl_gl_object_state_ptr_vec framebufferObjects; pContext->get_all_objects_of_category(cGLSTFramebuffer, framebufferObjects); - m_framebufferExplorer->set_framebuffer_objects(framebufferObjects, *pContext, pStateSnapshot->get_default_framebuffer()); + m_pFramebufferExplorer->set_framebuffer_objects(framebufferObjects, *pContext, pStateSnapshot->get_default_framebuffer()); GLuint64 curDrawFramebuffer = pContext->get_general_state().get_value(GL_DRAW_FRAMEBUFFER_BINDING); displayFramebuffer(curDrawFramebuffer, false); // programs vogl_gl_object_state_ptr_vec programObjects; pContext->get_all_objects_of_category(cGLSTProgram, programObjects); - m_programExplorer->set_program_objects(programObjects); + m_pProgramExplorer->set_program_objects(programObjects); GLuint64 curProgram = pContext->get_general_state().get_value(GL_CURRENT_PROGRAM); - m_programExplorer->set_active_program(curProgram); + m_pProgramExplorer->set_active_program(curProgram); if (programObjects.size() > 0) { VOGLEDITOR_ENABLE_TAB(ui->programTab); } // shaders vogl_gl_object_state_ptr_vec shaderObjects; pContext->get_all_objects_of_category(cGLSTShader, shaderObjects); - m_shaderExplorer->set_shader_objects(shaderObjects); + m_pShaderExplorer->set_shader_objects(shaderObjects); if (curProgram != 0) { for (vogl_gl_object_state_ptr_vec::iterator iter = programObjects.begin(); iter != programObjects.end(); iter++) @@ -1002,7 +1527,7 @@ void VoglEditor::update_ui_for_snapshot(vogleditor_gl_state_snapshot* pStateSnap if (pProgramState->get_attached_shaders().size() > 0) { uint curShader = pProgramState->get_attached_shaders()[0]; - m_shaderExplorer->set_active_shader(curShader); + m_pShaderExplorer->set_active_shader(curShader); } break; } @@ -1083,7 +1608,7 @@ void VoglEditor::on_stateTreeView_clicked(const QModelIndex &index) bool VoglEditor::displayShader(GLuint64 shaderHandle, bool bBringTabToFront) { bool bDisplayed = false; - if (m_shaderExplorer->set_active_shader(shaderHandle)) + if (m_pShaderExplorer->set_active_shader(shaderHandle)) { if (bBringTabToFront) { @@ -1096,7 +1621,7 @@ bool VoglEditor::displayShader(GLuint64 shaderHandle, bool bBringTabToFront) void VoglEditor::displayProgram(GLuint64 programHandle, bool bBringTabToFront) { - if (m_programExplorer->set_active_program(programHandle)) + if (m_pProgramExplorer->set_active_program(programHandle)) { if (bBringTabToFront) { @@ -1107,7 +1632,7 @@ void VoglEditor::displayProgram(GLuint64 programHandle, bool bBringTabToFront) void VoglEditor::displayFramebuffer(GLuint64 framebufferHandle, bool bBringTabToFront) { - bool bDisplayedFBO = m_framebufferExplorer->set_active_framebuffer(framebufferHandle); + bool bDisplayedFBO = m_pFramebufferExplorer->set_active_framebuffer(framebufferHandle); if (bDisplayedFBO) { @@ -1121,7 +1646,7 @@ void VoglEditor::displayFramebuffer(GLuint64 framebufferHandle, bool bBringTabTo bool VoglEditor::displayTexture(GLuint64 textureHandle, bool bBringTabToFront) { - bool bDisplayedTexture = m_textureExplorer->set_active_texture(textureHandle); + bool bDisplayedTexture = m_pTextureExplorer->set_active_texture(textureHandle); if (bDisplayedTexture) { @@ -1166,7 +1691,7 @@ void VoglEditor::onApiCallSelected(const QModelIndex &index, bool bAllowStateSna vogleditor_gl_state_snapshot* pNewSnapshot = NULL; QCursor origCursor = cursor(); setCursor(Qt::WaitCursor); - m_traceReplayer.replay(m_pTraceReader, m_pApicallTreeModel->root(), &pNewSnapshot, pApiCallItem->globalCallIndex(), false); + m_traceReplayer.replay(m_pTraceReader, m_pApiCallTreeModel->root(), &pNewSnapshot, pApiCallItem->globalCallIndex(), false); setCursor(origCursor); pCallTreeItem->set_snapshot(pNewSnapshot); } @@ -1239,132 +1764,103 @@ void VoglEditor::selectApicallModelIndex(QModelIndex index, bool scrollTo, bool { ui->treeView->setCurrentIndex(index); } - - if (m_searchApicallResults.size() > 0 && !ui->searchTextBox->text().isEmpty()) - { - QItemSelectionModel* pSelection = ui->treeView->selectionModel(); - for (int i = 0; i < m_searchApicallResults.size(); i++) - { - pSelection->select(m_searchApicallResults[i], QItemSelectionModel::Select | QItemSelectionModel::Rows); - } - ui->treeView->setSelectionModel(pSelection); - } } void VoglEditor::on_searchTextBox_textChanged(const QString &searchText) { - QModelIndex curSearchIndex = ui->treeView->currentIndex(); - if (curSearchIndex.isValid() == false) - { - return; - } - - // store original background color of the search text box so that it can be turned to red and later restored. - static const QColor sOriginalTextBoxBackground = ui->searchTextBox->palette().base().color(); - - // clear previous items - QItemSelectionModel* pSelection = ui->treeView->selectionModel(); - if (pSelection != NULL) - { - for (int i = 0; i < m_searchApicallResults.size(); i++) - { - pSelection->select(m_searchApicallResults[i], QItemSelectionModel::Clear | QItemSelectionModel::Rows); - } - ui->treeView->setSelectionModel(pSelection); - } + QPalette palette(ui->searchTextBox->palette()); + palette.setColor(QPalette::Base, m_searchTextboxBackgroundColor); + ui->searchTextBox->setPalette(palette); - // find new matches - m_searchApicallResults.clear(); - if (m_pApicallTreeModel != NULL) + if (m_pApiCallTreeModel != NULL) { - m_searchApicallResults = m_pApicallTreeModel->find_search_matches(searchText); + m_pApiCallTreeModel->set_highlight_search_string(searchText); } - // if there are matches, restore the textbox background to its original color - if (m_searchApicallResults.size() > 0) - { - QPalette palette(ui->searchTextBox->palette()); - palette.setColor(QPalette::Base, sOriginalTextBoxBackground); - ui->searchTextBox->setPalette(palette); - } - - // select new items - if (!searchText.isEmpty()) - { - if (m_searchApicallResults.size() > 0) - { - // scroll to the first result, but don't select it - selectApicallModelIndex(m_searchApicallResults[0], true, false); - } - else - { - // no items were found, so set the textbox background to red - QPalette palette(ui->searchTextBox->palette()); - palette.setColor(QPalette::Base, Qt::red); - ui->searchTextBox->setPalette(palette); - } - } + // need to briefly give the treeview focus so that it properly redraws and highlights the matching rows + // then return focus to the search textbox so that typed keys are not lost + ui->treeView->setFocus(); + ui->searchTextBox->setFocus(); } void VoglEditor::on_searchNextButton_clicked() { - if (m_pApicallTreeModel != NULL) + if (m_pApiCallTreeModel != NULL) { - QModelIndex index = m_pApicallTreeModel->find_next_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text()); - selectApicallModelIndex(index, true, true); + QModelIndex index = m_pApiCallTreeModel->find_next_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text()); + if (index.isValid()) + { + selectApicallModelIndex(index, true, true); + ui->treeView->setFocus(); + } } } void VoglEditor::on_searchPrevButton_clicked() { - if (m_pApicallTreeModel != NULL) + if (m_pApiCallTreeModel != NULL) { - QModelIndex index = m_pApicallTreeModel->find_prev_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text()); - selectApicallModelIndex(index, true, true); + QModelIndex index = m_pApiCallTreeModel->find_prev_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text()); + if (index.isValid()) + { + selectApicallModelIndex(index, true, true); + ui->treeView->setFocus(); + } } } void VoglEditor::on_prevSnapshotButton_clicked() { - if (m_pApicallTreeModel != NULL) + if (m_pApiCallTreeModel != NULL) { - vogleditor_apiCallTreeItem* pPrevItemWithSnapshot = m_pApicallTreeModel->find_prev_snapshot(m_pCurrentCallTreeItem); - selectApicallModelIndex(m_pApicallTreeModel->indexOf(pPrevItemWithSnapshot), true, true); - ui->treeView->setFocus(); + vogleditor_apiCallTreeItem* pPrevItemWithSnapshot = m_pApiCallTreeModel->find_prev_snapshot(m_pCurrentCallTreeItem); + if (pPrevItemWithSnapshot != NULL) + { + selectApicallModelIndex(m_pApiCallTreeModel->indexOf(pPrevItemWithSnapshot), true, true); + ui->treeView->setFocus(); + } } } void VoglEditor::on_nextSnapshotButton_clicked() { - if (m_pApicallTreeModel != NULL) + if (m_pApiCallTreeModel != NULL) { - vogleditor_apiCallTreeItem* pNextItemWithSnapshot = m_pApicallTreeModel->find_next_snapshot(m_pCurrentCallTreeItem); - selectApicallModelIndex(m_pApicallTreeModel->indexOf(pNextItemWithSnapshot), true, true); - ui->treeView->setFocus(); + vogleditor_apiCallTreeItem* pNextItemWithSnapshot = m_pApiCallTreeModel->find_next_snapshot(m_pCurrentCallTreeItem); + if (pNextItemWithSnapshot != NULL) + { + selectApicallModelIndex(m_pApiCallTreeModel->indexOf(pNextItemWithSnapshot), true, true); + ui->treeView->setFocus(); + } } } void VoglEditor::on_prevDrawcallButton_clicked() { - if (m_pApicallTreeModel != NULL) + if (m_pApiCallTreeModel != NULL) { - vogleditor_apiCallTreeItem* pPrevItem = m_pApicallTreeModel->find_prev_drawcall(m_pCurrentCallTreeItem); - selectApicallModelIndex(m_pApicallTreeModel->indexOf(pPrevItem), true, true); - ui->treeView->setFocus(); + vogleditor_apiCallTreeItem* pPrevItem = m_pApiCallTreeModel->find_prev_drawcall(m_pCurrentCallTreeItem); + if (pPrevItem != NULL) + { + selectApicallModelIndex(m_pApiCallTreeModel->indexOf(pPrevItem), true, true); + ui->treeView->setFocus(); + } } } void VoglEditor::on_nextDrawcallButton_clicked() { - if (m_pApicallTreeModel != NULL) + if (m_pApiCallTreeModel != NULL) { - vogleditor_apiCallTreeItem* pNextItem = m_pApicallTreeModel->find_next_drawcall(m_pCurrentCallTreeItem); - selectApicallModelIndex(m_pApicallTreeModel->indexOf(pNextItem), true, true); - ui->treeView->setFocus(); + vogleditor_apiCallTreeItem* pNextItem = m_pApiCallTreeModel->find_next_drawcall(m_pCurrentCallTreeItem); + if (pNextItem != NULL) + { + selectApicallModelIndex(m_pApiCallTreeModel->indexOf(pNextItem), true, true); + ui->treeView->setFocus(); + } } } - void VoglEditor::on_program_edited(vogl_program_state* pNewProgramState) { VOGL_NOTE_UNUSED(pNewProgramState); @@ -1373,7 +1869,7 @@ void VoglEditor::on_program_edited(vogl_program_state* pNewProgramState) // update all the snapshot flags bool bFoundEditedSnapshot = false; - recursive_update_snapshot_flags(m_pApicallTreeModel->root(), bFoundEditedSnapshot); + recursive_update_snapshot_flags(m_pApiCallTreeModel->root(), bFoundEditedSnapshot); // 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, // 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 @@ -1413,3 +1909,58 @@ void VoglEditor::recursive_update_snapshot_flags(vogleditor_apiCallTreeItem* pIt #undef VOGLEDITOR_DISABLE_TAB #undef VOGLEDITOR_ENABLE_TAB + +void VoglEditor::on_actionSave_Session_triggered() +{ + QString baseName = m_openFilename; + + int lastIndex = baseName.lastIndexOf('.'); + if (lastIndex != -1) + { + baseName = baseName.remove(lastIndex, baseName.size() - lastIndex); + } + + QString suggestedName = baseName + "-vogleditor.json"; + + QString sessionFilename = QFileDialog::getSaveFileName(this, tr("Save Debug Session"), suggestedName, tr("JSON (*.json)")); + + if (!save_session_to_disk(sessionFilename)) + { + m_pStatusLabel->setText("ERROR: Failed to save session"); + } +} + +void VoglEditor::on_actionOpen_Session_triggered() +{ + QString sessionFilename = QFileDialog::getOpenFileName(this, tr("Load Debug Session"), QString(), tr("JSON (*.json)")); + + QCursor origCursor = this->cursor(); + setCursor(Qt::WaitCursor); + + if (!load_session_from_disk(sessionFilename)) + { + m_pStatusLabel->setText("ERROR: Failed to load session"); + } + + setCursor(origCursor); +} + +void VoglEditor::on_searchTextBox_returnPressed() +{ + if (m_pApiCallTreeModel != NULL) + { + QModelIndex index = m_pApiCallTreeModel->find_next_search_result(m_pCurrentCallTreeItem, ui->searchTextBox->text()); + if (index.isValid()) + { + // a valid item was found, scroll to it and select it + selectApicallModelIndex(index, true, true); + } + else + { + // 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) + QPalette palette(ui->searchTextBox->palette()); + palette.setColor(QPalette::Base, Qt::red); + ui->searchTextBox->setPalette(palette); + } + } +}