SkDebuggerGUI.cpp revision 8a1cdaece7e1d009befb84f21bb82370025bf4d6
1/* 2 * Copyright 2012 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8#include "SkDebuggerGUI.h" 9#include "SkGraphics.h" 10#include "SkImageDecoder.h" 11#include <QListWidgetItem> 12#include "PictureRenderer.h" 13#include "SkPictureRecord.h" 14#include "SkPicturePlayback.h" 15#include "BenchTimer.h" 16 17SkDebuggerGUI::SkDebuggerGUI(QWidget *parent) : 18 QMainWindow(parent) 19 , fCentralWidget(this) 20 , fStatusBar(this) 21 , fToolBar(this) 22 , fActionOpen(this) 23 , fActionBreakpoint(this) 24 , fActionProfile(this) 25 , fActionCancel(this) 26 , fActionClearBreakpoints(this) 27 , fActionClearDeletes(this) 28 , fActionClose(this) 29 , fActionCreateBreakpoint(this) 30 , fActionDelete(this) 31 , fActionDirectory(this) 32 , fActionGoToLine(this) 33 , fActionInspector(this) 34 , fActionPlay(this) 35 , fActionPause(this) 36 , fActionRewind(this) 37 , fActionSave(this) 38 , fActionSaveAs(this) 39 , fActionShowDeletes(this) 40 , fActionStepBack(this) 41 , fActionStepForward(this) 42 , fActionZoomIn(this) 43 , fActionZoomOut(this) 44 , fMapper(this) 45 , fListWidget(&fCentralWidget) 46 , fDirectoryWidget(&fCentralWidget) 47 , fCanvasWidget(this, &fDebugger) 48 , fMenuBar(this) 49 , fMenuFile(this) 50 , fMenuNavigate(this) 51 , fMenuView(this) 52 , fBreakpointsActivated(false) 53 , fDeletesActivated(false) 54 , fPause(false) 55 , fLoading(false) 56{ 57 setupUi(this); 58 connect(&fListWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(registerListClick(QListWidgetItem *))); 59 connect(&fActionOpen, SIGNAL(triggered()), this, SLOT(openFile())); 60 connect(&fActionDirectory, SIGNAL(triggered()), this, SLOT(toggleDirectory())); 61 connect(&fDirectoryWidget, SIGNAL(currentItemChanged(QListWidgetItem*, QListWidgetItem*)), this, SLOT(loadFile(QListWidgetItem *))); 62 connect(&fActionDelete, SIGNAL(triggered()), this, SLOT(actionDelete())); 63 connect(&fListWidget, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(toggleBreakpoint())); 64 connect(&fActionRewind, SIGNAL(triggered()), this, SLOT(actionRewind())); 65 connect(&fActionPlay, SIGNAL(triggered()), this, SLOT(actionPlay())); 66 connect(&fActionStepBack, SIGNAL(triggered()), this, SLOT(actionStepBack())); 67 connect(&fActionStepForward, SIGNAL(triggered()), this, SLOT(actionStepForward())); 68 connect(&fActionBreakpoint, SIGNAL(triggered()), this, SLOT(actionBreakpoints())); 69 connect(&fActionInspector, SIGNAL(triggered()), this, SLOT(actionInspector())); 70 connect(&fActionInspector, SIGNAL(triggered()), this, SLOT(actionSettings())); 71 connect(&fFilter, SIGNAL(activated(QString)), this, SLOT(toggleFilter(QString))); 72 connect(&fActionProfile, SIGNAL(triggered()), this, SLOT(actionProfile())); 73 connect(&fActionCancel, SIGNAL(triggered()), this, SLOT(actionCancel())); 74 connect(&fActionClearBreakpoints, SIGNAL(triggered()), this, SLOT(actionClearBreakpoints())); 75 connect(&fActionClearDeletes, SIGNAL(triggered()), this, SLOT(actionClearDeletes())); 76 connect(&fActionClose, SIGNAL(triggered()), this, SLOT(actionClose())); 77 connect(fSettingsWidget.getVisibilityButton(), SIGNAL(toggled(bool)), this, SLOT(actionCommandFilter())); 78 connect(fSettingsWidget.getGLCheckBox(), SIGNAL(toggled(bool)), this, SLOT(actionGLWidget(bool))); 79 connect(fSettingsWidget.getRasterCheckBox(), SIGNAL(toggled(bool)), this, SLOT(actionRasterWidget(bool))); 80 connect(&fActionPause, SIGNAL(toggled(bool)), this, SLOT(pauseDrawing(bool))); 81 connect(&fActionCreateBreakpoint, SIGNAL(activated()), this, SLOT(toggleBreakpoint())); 82 connect(&fActionShowDeletes, SIGNAL(triggered()), this, SLOT(showDeletes())); 83 connect(&fCanvasWidget, SIGNAL(hitChanged(int)), this, SLOT(selectCommand(int))); 84 connect(&fCanvasWidget, SIGNAL(hitChanged(int)), &fSettingsWidget, SLOT(updateHit(int))); 85 connect(&fCanvasWidget, SIGNAL(scaleFactorChanged(float)), this, SLOT(actionScale(float))); 86 connect(&fCanvasWidget, SIGNAL(commandChanged(int)), &fSettingsWidget, SLOT(updateCommand(int))); 87 connect(&fActionSaveAs, SIGNAL(triggered()), this, SLOT(actionSaveAs())); 88 connect(&fActionSave, SIGNAL(triggered()), this, SLOT(actionSave())); 89 90 fMapper.setMapping(&fActionZoomIn, 1); 91 fMapper.setMapping(&fActionZoomOut, -1); 92 93 connect(&fActionZoomIn, SIGNAL(triggered()), &fMapper, SLOT(map())); 94 connect(&fActionZoomOut, SIGNAL(triggered()), &fMapper, SLOT(map())); 95 connect(&fMapper, SIGNAL(mapped(int)), &fCanvasWidget, SLOT(keyZoom(int))); 96 97 fInspectorWidget.setDisabled(true); 98 fMenuEdit.setDisabled(true); 99 fMenuNavigate.setDisabled(true); 100 fMenuView.setDisabled(true); 101 102 SkGraphics::Init(); 103} 104 105SkDebuggerGUI::~SkDebuggerGUI() { 106 SkGraphics::Term(); 107} 108 109void SkDebuggerGUI::actionBreakpoints() { 110 fBreakpointsActivated = !fBreakpointsActivated; 111 for (int row = 0; row < fListWidget.count(); row++) { 112 QListWidgetItem *item = fListWidget.item(row); 113 item->setHidden(item->checkState() == Qt::Unchecked && fBreakpointsActivated); 114 } 115} 116 117void SkDebuggerGUI::showDeletes() { 118 fDeletesActivated = !fDeletesActivated; 119 for (int row = 0; row < fListWidget.count(); row++) { 120 QListWidgetItem *item = fListWidget.item(row); 121 item->setHidden(fDebugger.isCommandVisible(row) 122 && fDeletesActivated); 123 } 124} 125 126// The timed picture playback uses the SkPicturePlayback's profiling stubs 127// to time individual commands. The offsets are needed to map SkPicture 128// offsets to individual commands. 129class SkTimedPicturePlayback : public SkPicturePlayback { 130public: 131 SkTimedPicturePlayback(SkStream* stream, const SkPictInfo& info, bool* isValid, 132 SkSerializationHelpers::DecodeBitmap decoder, 133 const SkTDArray<size_t>& offsets) 134 : INHERITED(stream, info, isValid, decoder) 135 , fTot(0.0) 136 , fCurCommand(0) 137 , fOffsets(offsets) { 138 fTimes.setCount(fOffsets.count()); 139 fTypeTimes.setCount(LAST_DRAWTYPE_ENUM+1); 140 this->resetTimes(); 141 } 142 143 void resetTimes() { 144 for (int i = 0; i < fOffsets.count(); ++i) { 145 fTimes[i] = 0.0; 146 } 147 for (int i = 0; i < fTypeTimes.count(); ++i) { 148 fTypeTimes[i] = 0.0f; 149 } 150 fTot = 0.0; 151 } 152 153 int count() const { return fTimes.count(); } 154 155 double time(int index) const { return fTimes[index] / fTot; } 156 157 const SkTDArray<double>* typeTimes() const { return &fTypeTimes; } 158 159 double totTime() const { return fTot; } 160 161protected: 162 BenchTimer fTimer; 163 SkTDArray<size_t> fOffsets; // offset in the SkPicture for each command 164 SkTDArray<double> fTimes; // sum of time consumed for each command 165 SkTDArray<double> fTypeTimes; // sum of time consumed for each type of command (e.g., drawPath) 166 double fTot; // total of all times in 'fTimes' 167 size_t fCurOffset; 168 int fCurType; 169 int fCurCommand; // the current command being executed/timed 170 171 virtual void preDraw(size_t offset, int type) { 172 // This search isn't as bad as it seems. In normal playback mode, the 173 // base class steps through the commands in order and can only skip ahead 174 // a bit on a clip. This class is only used during profiling so we 175 // don't have to worry about forward/backward scrubbing through commands. 176 for (int i = 0; offset != fOffsets[fCurCommand]; ++i) { 177 fCurCommand = (fCurCommand+1) % fOffsets.count(); 178 SkASSERT(i <= fOffsets.count()); // should always find the offset in the list 179 } 180 181 fCurOffset = offset; 182 fCurType = type; 183 // The SkDebugCanvas doesn't recognize these types. This class needs to 184 // convert or else we'll wind up with a mismatch between the type counts 185 // the debugger displays and the profile times. 186 if (DRAW_POS_TEXT_TOP_BOTTOM == type) { 187 fCurType = DRAW_POS_TEXT; 188 } else if (DRAW_POS_TEXT_H_TOP_BOTTOM == type) { 189 fCurType = DRAW_POS_TEXT_H; 190 } 191 192 fTimer.start(); 193 } 194 195 virtual void postDraw(size_t offset) { 196 fTimer.end(); 197 198 SkASSERT(offset == fCurOffset); 199 SkASSERT(fCurType <= LAST_DRAWTYPE_ENUM); 200 201#if defined(SK_BUILD_FOR_WIN32) 202 // CPU timer doesn't work well on Windows 203 fTimes[fCurCommand] += fTimer.fWall; 204 fTypeTimes[fCurType] += fTimer.fWall; 205 fTot += fTimer.fWall; 206#else 207 fTimes[fCurCommand] += fTimer.fCpu; 208 fTypeTimes[fCurType] += fTimer.fCpu; 209 fTot += fTimer.fCpu; 210#endif 211 } 212 213private: 214 typedef SkPicturePlayback INHERITED; 215}; 216 217// Wrap SkPicture to allow installation of an SkTimedPicturePlayback object 218class SkTimedPicture : public SkPicture { 219public: 220 explicit SkTimedPicture(SkStream* stream, 221 bool* success, 222 SkSerializationHelpers::DecodeBitmap decoder, 223 const SkTDArray<size_t>& offsets) { 224 if (success) { 225 *success = false; 226 } 227 fRecord = NULL; 228 fPlayback = NULL; 229 fWidth = fHeight = 0; 230 231 SkPictInfo info; 232 233 if (!stream->read(&info, sizeof(info))) { 234 return; 235 } 236 if (SkPicture::PICTURE_VERSION != info.fVersion) { 237 return; 238 } 239 240 if (stream->readBool()) { 241 bool isValid = false; 242 fPlayback = SkNEW_ARGS(SkTimedPicturePlayback, 243 (stream, info, &isValid, decoder, offsets)); 244 if (!isValid) { 245 SkDELETE(fPlayback); 246 fPlayback = NULL; 247 return; 248 } 249 } 250 251 // do this at the end, so that they will be zero if we hit an error. 252 fWidth = info.fWidth; 253 fHeight = info.fHeight; 254 if (success) { 255 *success = true; 256 } 257 } 258 259 void resetTimes() { ((SkTimedPicturePlayback*) fPlayback)->resetTimes(); } 260 261 int count() const { return ((SkTimedPicturePlayback*) fPlayback)->count(); } 262 263 // return the fraction of the total time this command consumed 264 double time(int index) const { return ((SkTimedPicturePlayback*) fPlayback)->time(index); } 265 266 const SkTDArray<double>* typeTimes() const { return ((SkTimedPicturePlayback*) fPlayback)->typeTimes(); } 267 268 double totTime() const { return ((SkTimedPicturePlayback*) fPlayback)->totTime(); } 269 270private: 271 // disallow default ctor b.c. we don't have a good way to setup the fPlayback ptr 272 SkTimedPicture(); 273 // disallow the copy ctor - enabling would require copying code from SkPicture 274 SkTimedPicture(const SkTimedPicture& src); 275 276 typedef SkPicture INHERITED; 277}; 278 279// This is a simplification of PictureBenchmark's run with the addition of 280// clearing of the times after the first pass (in resetTimes) 281void SkDebuggerGUI::run(SkTimedPicture* pict, 282 sk_tools::PictureRenderer* renderer, 283 int repeats) { 284 SkASSERT(pict); 285 if (NULL == pict) { 286 return; 287 } 288 289 SkASSERT(renderer != NULL); 290 if (NULL == renderer) { 291 return; 292 } 293 294 renderer->init(pict); 295 296 renderer->setup(); 297 renderer->render(NULL); 298 renderer->resetState(); 299 300 // We throw this away the first batch of times to remove first time effects (such as paging in this program) 301 pict->resetTimes(); 302 303 for (int i = 0; i < repeats; ++i) { 304 renderer->setup(); 305 renderer->render(NULL); 306 renderer->resetState(); 307 } 308 309 renderer->end(); 310} 311 312void SkDebuggerGUI::actionProfile() { 313 // In order to profile we pass the command offsets (that were read-in 314 // in loadPicture by the SkOffsetPicture) to an SkTimedPlaybackPicture. 315 // The SkTimedPlaybackPicture in turn passes the offsets to an 316 // SkTimedPicturePlayback object which uses them to track the performance 317 // of individual commands. 318 if (fFileName.isEmpty()) { 319 return; 320 } 321 322 SkFILEStream inputStream; 323 324 inputStream.setPath(fFileName.c_str()); 325 if (!inputStream.isValid()) { 326 return; 327 } 328 329 bool success = false; 330 SkTimedPicture picture(&inputStream, &success, &SkImageDecoder::DecodeStream, fOffsets); 331 if (!success) { 332 return; 333 } 334 335 // For now this #if allows switching between tiled and simple rendering 336 // modes. Eventually this will be accomplished via the GUI 337#if 1 338 sk_tools::TiledPictureRenderer* renderer = NULL; 339 340 renderer = SkNEW(sk_tools::TiledPictureRenderer); 341 renderer->setTileWidth(256); 342 renderer->setTileHeight(256); 343#else 344 sk_tools::SimplePictureRenderer* renderer = NULL; 345 346 renderer = SkNEW(sk_tools::SimplePictureRenderer); 347#endif 348 349 run(&picture, renderer, 2); 350 351 SkASSERT(picture.count() == fListWidget.count()); 352 353 // extract the individual command times from the SkTimedPlaybackPicture 354 for (int i = 0; i < picture.count(); ++i) { 355 double temp = picture.time(i); 356 357 QListWidgetItem* item = fListWidget.item(i); 358 359 item->setData(Qt::UserRole + 4, 100.0*temp); 360 } 361 362 setupOverviewText(picture.typeTimes(), picture.totTime()); 363} 364 365void SkDebuggerGUI::actionCancel() { 366 for (int row = 0; row < fListWidget.count(); row++) { 367 fListWidget.item(row)->setHidden(false); 368 } 369} 370 371void SkDebuggerGUI::actionClearBreakpoints() { 372 for (int row = 0; row < fListWidget.count(); row++) { 373 QListWidgetItem* item = fListWidget.item(row); 374 item->setCheckState(Qt::Unchecked); 375 item->setData(Qt::DecorationRole, 376 QPixmap(":/blank.png")); 377 } 378} 379 380void SkDebuggerGUI::actionClearDeletes() { 381 for (int row = 0; row < fListWidget.count(); row++) { 382 QListWidgetItem* item = fListWidget.item(row); 383 item->setData(Qt::UserRole + 2, QPixmap(":/blank.png")); 384 fDebugger.setCommandVisible(row, true); 385 } 386 if (fPause) { 387 fCanvasWidget.drawTo(fPausedRow); 388 } else { 389 fCanvasWidget.drawTo(fListWidget.currentRow()); 390 } 391} 392 393void SkDebuggerGUI::actionCommandFilter() { 394 fDebugger.highlightCurrentCommand( 395 fSettingsWidget.getVisibilityButton()->isChecked()); 396 fCanvasWidget.drawTo(fListWidget.currentRow()); 397} 398 399void SkDebuggerGUI::actionClose() { 400 this->close(); 401} 402 403void SkDebuggerGUI::actionDelete() { 404 int currentRow = fListWidget.currentRow(); 405 QListWidgetItem* item = fListWidget.currentItem(); 406 407 if (fDebugger.isCommandVisible(currentRow)) { 408 item->setData(Qt::UserRole + 2, QPixmap(":/delete.png")); 409 fDebugger.setCommandVisible(currentRow, false); 410 } else { 411 item->setData(Qt::UserRole + 2, QPixmap(":/blank.png")); 412 fDebugger.setCommandVisible(currentRow, true); 413 } 414 415 if (fPause) { 416 fCanvasWidget.drawTo(fPausedRow); 417 } else { 418 fCanvasWidget.drawTo(currentRow); 419 } 420} 421 422void SkDebuggerGUI::actionGLWidget(bool isToggled) { 423 fCanvasWidget.setWidgetVisibility(SkCanvasWidget::kGPU_WidgetType, !isToggled); 424} 425 426void SkDebuggerGUI::actionInspector() { 427 if (fInspectorWidget.isHidden()) { 428 fInspectorWidget.setHidden(false); 429 } else { 430 fInspectorWidget.setHidden(true); 431 } 432} 433 434void SkDebuggerGUI::actionPlay() { 435 for (int row = fListWidget.currentRow() + 1; row < fListWidget.count(); 436 row++) { 437 QListWidgetItem *item = fListWidget.item(row); 438 if (item->checkState() == Qt::Checked) { 439 fListWidget.setCurrentItem(item); 440 return; 441 } 442 } 443 fListWidget.setCurrentRow(fListWidget.count() - 1); 444} 445 446void SkDebuggerGUI::actionRasterWidget(bool isToggled) { 447 fCanvasWidget.setWidgetVisibility(SkCanvasWidget::kRaster_8888_WidgetType, !isToggled); 448} 449 450void SkDebuggerGUI::actionRewind() { 451 fListWidget.setCurrentRow(0); 452} 453 454void SkDebuggerGUI::actionSave() { 455 fFileName = fPath.toAscii(); 456 fFileName.append("/"); 457 fFileName.append(fDirectoryWidget.currentItem()->text().toAscii()); 458 saveToFile(fFileName); 459} 460 461void SkDebuggerGUI::actionSaveAs() { 462 QString filename = QFileDialog::getSaveFileName(this, "Save File", "", 463 "Skia Picture (*skp)"); 464 if (!filename.endsWith(".skp", Qt::CaseInsensitive)) { 465 filename.append(".skp"); 466 } 467 saveToFile(SkString(filename.toAscii().data())); 468} 469 470void SkDebuggerGUI::actionScale(float scaleFactor) { 471 fSettingsWidget.setZoomText(scaleFactor); 472} 473 474void SkDebuggerGUI::actionSettings() { 475 if (fSettingsWidget.isHidden()) { 476 fSettingsWidget.setHidden(false); 477 } else { 478 fSettingsWidget.setHidden(true); 479 } 480} 481 482void SkDebuggerGUI::actionStepBack() { 483 int currentRow = fListWidget.currentRow(); 484 if (currentRow != 0) { 485 fListWidget.setCurrentRow(currentRow - 1); 486 } 487} 488 489void SkDebuggerGUI::actionStepForward() { 490 int currentRow = fListWidget.currentRow(); 491 QString curRow = QString::number(currentRow); 492 QString curCount = QString::number(fListWidget.count()); 493 if (currentRow < fListWidget.count() - 1) { 494 fListWidget.setCurrentRow(currentRow + 1); 495 } 496} 497 498void SkDebuggerGUI::drawComplete() { 499 fInspectorWidget.setMatrix(fDebugger.getCurrentMatrix()); 500 fInspectorWidget.setClip(fDebugger.getCurrentClip()); 501} 502 503void SkDebuggerGUI::saveToFile(const SkString& filename) { 504 SkFILEWStream file(filename.c_str()); 505 fDebugger.makePicture()->serialize(&file); 506} 507 508void SkDebuggerGUI::loadFile(QListWidgetItem *item) { 509 if (fDirectoryWidgetActive) { 510 fFileName = fPath.toAscii(); 511 fFileName.append("/"); 512 fFileName.append(item->text().toAscii()); 513 loadPicture(fFileName); 514 } 515} 516 517void SkDebuggerGUI::openFile() { 518 QString temp = QFileDialog::getOpenFileName(this, tr("Open File"), "", 519 tr("Files (*.*)")); 520 fDirectoryWidgetActive = false; 521 if (!temp.isEmpty()) { 522 QFileInfo pathInfo(temp); 523 fPath = pathInfo.path(); 524 loadPicture(SkString(temp.toAscii().data())); 525 setupDirectoryWidget(); 526 } 527 fDirectoryWidgetActive = true; 528} 529 530void SkDebuggerGUI::pauseDrawing(bool isPaused) { 531 fPause = isPaused; 532 fPausedRow = fListWidget.currentRow(); 533 fCanvasWidget.drawTo(fPausedRow); 534} 535 536void SkDebuggerGUI::registerListClick(QListWidgetItem *item) { 537 if(!fLoading) { 538 int currentRow = fListWidget.currentRow(); 539 540 if (currentRow != -1) { 541 if (!fPause) { 542 fCanvasWidget.drawTo(currentRow); 543 } 544 SkTDArray<SkString*> *currInfo = fDebugger.getCommandInfo( 545 currentRow); 546 547 /* TODO(chudy): Add command type before parameters. Rename v 548 * to something more informative. */ 549 if (currInfo) { 550 QString info; 551 info.append("<b>Parameters: </b><br/>"); 552 for (int i = 0; i < currInfo->count(); i++) { 553 554 info.append(QString((*currInfo)[i]->c_str())); 555 info.append("<br/>"); 556 } 557 fInspectorWidget.setText(info, SkInspectorWidget::kDetail_TabType); 558 fInspectorWidget.setDisabled(false); 559 } 560 } 561 562 } 563} 564 565void SkDebuggerGUI::selectCommand(int command) { 566 if (fPause) { 567 fListWidget.setCurrentRow(command); 568 } 569} 570 571void SkDebuggerGUI::toggleBreakpoint() { 572 QListWidgetItem* item = fListWidget.currentItem(); 573 if (item->checkState() == Qt::Unchecked) { 574 item->setCheckState(Qt::Checked); 575 item->setData(Qt::DecorationRole, 576 QPixmap(":/breakpoint_16x16.png")); 577 } else { 578 item->setCheckState(Qt::Unchecked); 579 item->setData(Qt::DecorationRole, 580 QPixmap(":/blank.png")); 581 } 582} 583 584void SkDebuggerGUI::toggleDirectory() { 585 fDirectoryWidget.setHidden(!fDirectoryWidget.isHidden()); 586} 587 588void SkDebuggerGUI::toggleFilter(QString string) { 589 for (int row = 0; row < fListWidget.count(); row++) { 590 QListWidgetItem *item = fListWidget.item(row); 591 item->setHidden(item->text() != string); 592 } 593} 594 595void SkDebuggerGUI::setupUi(QMainWindow *SkDebuggerGUI) { 596 QIcon windowIcon; 597 windowIcon.addFile(QString::fromUtf8(":/skia.png"), QSize(), 598 QIcon::Normal, QIcon::Off); 599 SkDebuggerGUI->setObjectName(QString::fromUtf8("SkDebuggerGUI")); 600 SkDebuggerGUI->resize(1200, 1000); 601 SkDebuggerGUI->setWindowIcon(windowIcon); 602 SkDebuggerGUI->setWindowTitle("Skia Debugger"); 603 604 fActionOpen.setShortcuts(QKeySequence::Open); 605 fActionOpen.setText("Open"); 606 607 QIcon breakpoint; 608 breakpoint.addFile(QString::fromUtf8(":/breakpoint.png"), 609 QSize(), QIcon::Normal, QIcon::Off); 610 fActionBreakpoint.setShortcut(QKeySequence(tr("Ctrl+B"))); 611 fActionBreakpoint.setIcon(breakpoint); 612 fActionBreakpoint.setText("Breakpoints"); 613 614 QIcon cancel; 615 cancel.addFile(QString::fromUtf8(":/reload.png"), QSize(), 616 QIcon::Normal, QIcon::Off); 617 fActionCancel.setIcon(cancel); 618 fActionCancel.setText("Clear Filter"); 619 620 fActionClearBreakpoints.setShortcut(QKeySequence(tr("Alt+B"))); 621 fActionClearBreakpoints.setText("Clear Breakpoints"); 622 623 fActionClearDeletes.setShortcut(QKeySequence(tr("Alt+X"))); 624 fActionClearDeletes.setText("Clear Deletes"); 625 626 fActionClose.setShortcuts(QKeySequence::Quit); 627 fActionClose.setText("Exit"); 628 629 fActionCreateBreakpoint.setShortcut(QKeySequence(tr("B"))); 630 fActionCreateBreakpoint.setText("Set Breakpoint"); 631 632 fActionDelete.setShortcut(QKeySequence(tr("X"))); 633 fActionDelete.setText("Delete Command"); 634 635 fActionDirectory.setShortcut(QKeySequence(tr("Ctrl+D"))); 636 fActionDirectory.setText("Directory"); 637 638 QIcon profile; 639 profile.addFile(QString::fromUtf8(":/profile.png"), QSize(), 640 QIcon::Normal, QIcon::Off); 641 fActionProfile.setIcon(profile); 642 fActionProfile.setText("Profile"); 643 fActionProfile.setDisabled(true); 644 645 QIcon inspector; 646 inspector.addFile(QString::fromUtf8(":/inspector.png"), 647 QSize(), QIcon::Normal, QIcon::Off); 648 fActionInspector.setShortcut(QKeySequence(tr("Ctrl+I"))); 649 fActionInspector.setIcon(inspector); 650 fActionInspector.setText("Inspector"); 651 652 QIcon play; 653 play.addFile(QString::fromUtf8(":/play.png"), QSize(), 654 QIcon::Normal, QIcon::Off); 655 fActionPlay.setShortcut(QKeySequence(tr("Ctrl+P"))); 656 fActionPlay.setIcon(play); 657 fActionPlay.setText("Play"); 658 659 QIcon pause; 660 pause.addFile(QString::fromUtf8(":/pause.png"), QSize(), 661 QIcon::Normal, QIcon::Off); 662 fActionPause.setShortcut(QKeySequence(tr("Space"))); 663 fActionPause.setCheckable(true); 664 fActionPause.setIcon(pause); 665 fActionPause.setText("Pause"); 666 667 QIcon rewind; 668 rewind.addFile(QString::fromUtf8(":/rewind.png"), QSize(), 669 QIcon::Normal, QIcon::Off); 670 fActionRewind.setShortcut(QKeySequence(tr("Ctrl+R"))); 671 fActionRewind.setIcon(rewind); 672 fActionRewind.setText("Rewind"); 673 674 fActionSave.setShortcut(QKeySequence::Save); 675 fActionSave.setText("Save"); 676 fActionSave.setDisabled(true); 677 fActionSaveAs.setShortcut(QKeySequence::SaveAs); 678 fActionSaveAs.setText("Save As"); 679 fActionSaveAs.setDisabled(true); 680 681 fActionShowDeletes.setShortcut(QKeySequence(tr("Ctrl+X"))); 682 fActionShowDeletes.setText("Deleted Commands"); 683 684 QIcon stepBack; 685 stepBack.addFile(QString::fromUtf8(":/previous.png"), QSize(), 686 QIcon::Normal, QIcon::Off); 687 fActionStepBack.setShortcut(QKeySequence(tr("["))); 688 fActionStepBack.setIcon(stepBack); 689 fActionStepBack.setText("Step Back"); 690 691 QIcon stepForward; 692 stepForward.addFile(QString::fromUtf8(":/next.png"), 693 QSize(), QIcon::Normal, QIcon::Off); 694 fActionStepForward.setShortcut(QKeySequence(tr("]"))); 695 fActionStepForward.setIcon(stepForward); 696 fActionStepForward.setText("Step Forward"); 697 698 fActionZoomIn.setShortcut(QKeySequence(tr("Ctrl+="))); 699 fActionZoomIn.setText("Zoom In"); 700 fActionZoomOut.setShortcut(QKeySequence(tr("Ctrl+-"))); 701 fActionZoomOut.setText("Zoom Out"); 702 703 fListWidget.setItemDelegate(new SkListWidget(&fListWidget)); 704 fListWidget.setObjectName(QString::fromUtf8("listWidget")); 705 fListWidget.setMaximumWidth(250); 706 707 fFilter.addItem("--Filter By Available Commands--"); 708 709 fDirectoryWidget.setMaximumWidth(250); 710 fDirectoryWidget.setStyleSheet("QListWidget::Item {padding: 5px;}"); 711 712 fCanvasWidget.setSizePolicy(QSizePolicy::Expanding, 713 QSizePolicy::Expanding); 714 715 fInspectorWidget.setSizePolicy(QSizePolicy::Expanding, 716 QSizePolicy::Expanding); 717 fInspectorWidget.setMaximumHeight(300); 718 719 fSettingsWidget.setSizePolicy(QSizePolicy::Expanding, 720 QSizePolicy::Expanding); 721 fSettingsWidget.setMaximumWidth(250); 722 723 fLeftColumnLayout.setSpacing(6); 724 fLeftColumnLayout.addWidget(&fListWidget); 725 fLeftColumnLayout.addWidget(&fDirectoryWidget); 726 727 fCanvasAndSettingsLayout.setSpacing(6); 728 fCanvasAndSettingsLayout.addWidget(&fCanvasWidget); 729 fCanvasAndSettingsLayout.addWidget(&fSettingsWidget); 730 731 fMainAndRightColumnLayout.setSpacing(6); 732 fMainAndRightColumnLayout.addLayout(&fCanvasAndSettingsLayout); 733 fMainAndRightColumnLayout.addWidget(&fInspectorWidget); 734 735 fCentralWidget.setLayout(&fContainerLayout); 736 fContainerLayout.setSpacing(6); 737 fContainerLayout.setContentsMargins(11, 11, 11, 11); 738 fContainerLayout.addLayout(&fLeftColumnLayout); 739 fContainerLayout.addLayout(&fMainAndRightColumnLayout); 740 741 SkDebuggerGUI->setCentralWidget(&fCentralWidget); 742 SkDebuggerGUI->setStatusBar(&fStatusBar); 743 744 fToolBar.setIconSize(QSize(32, 32)); 745 fToolBar.setToolButtonStyle(Qt::ToolButtonTextUnderIcon); 746 SkDebuggerGUI->addToolBar(Qt::TopToolBarArea, &fToolBar); 747 748 fSpacer.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 749 750 fToolBar.addAction(&fActionRewind); 751 fToolBar.addAction(&fActionStepBack); 752 fToolBar.addAction(&fActionPause); 753 fToolBar.addAction(&fActionStepForward); 754 fToolBar.addAction(&fActionPlay); 755 fToolBar.addSeparator(); 756 fToolBar.addAction(&fActionInspector); 757 fToolBar.addSeparator(); 758 fToolBar.addAction(&fActionProfile); 759 760 fToolBar.addSeparator(); 761 fToolBar.addWidget(&fSpacer); 762 fToolBar.addWidget(&fFilter); 763 fToolBar.addAction(&fActionCancel); 764 765 // TODO(chudy): Remove static call. 766 fDirectoryWidgetActive = false; 767 fPath = ""; 768 fFileName = ""; 769 setupDirectoryWidget(); 770 fDirectoryWidgetActive = true; 771 772 // Menu Bar 773 fMenuFile.setTitle("File"); 774 fMenuFile.addAction(&fActionOpen); 775 fMenuFile.addAction(&fActionSave); 776 fMenuFile.addAction(&fActionSaveAs); 777 fMenuFile.addAction(&fActionClose); 778 779 fMenuEdit.setTitle("Edit"); 780 fMenuEdit.addAction(&fActionDelete); 781 fMenuEdit.addAction(&fActionClearDeletes); 782 fMenuEdit.addSeparator(); 783 fMenuEdit.addAction(&fActionCreateBreakpoint); 784 fMenuEdit.addAction(&fActionClearBreakpoints); 785 786 fMenuNavigate.setTitle("Navigate"); 787 fMenuNavigate.addAction(&fActionRewind); 788 fMenuNavigate.addAction(&fActionStepBack); 789 fMenuNavigate.addAction(&fActionStepForward); 790 fMenuNavigate.addAction(&fActionPlay); 791 fMenuNavigate.addAction(&fActionPause); 792 fMenuNavigate.addAction(&fActionGoToLine); 793 794 fMenuView.setTitle("View"); 795 fMenuView.addAction(&fActionBreakpoint); 796 fMenuView.addAction(&fActionShowDeletes); 797 fMenuView.addAction(&fActionZoomIn); 798 fMenuView.addAction(&fActionZoomOut); 799 800 fMenuWindows.setTitle("Window"); 801 fMenuWindows.addAction(&fActionInspector); 802 fMenuWindows.addAction(&fActionDirectory); 803 804 fActionGoToLine.setText("Go to Line..."); 805 fActionGoToLine.setDisabled(true); 806 fMenuBar.addAction(fMenuFile.menuAction()); 807 fMenuBar.addAction(fMenuEdit.menuAction()); 808 fMenuBar.addAction(fMenuView.menuAction()); 809 fMenuBar.addAction(fMenuNavigate.menuAction()); 810 fMenuBar.addAction(fMenuWindows.menuAction()); 811 812 fPause = false; 813 814 SkDebuggerGUI->setMenuBar(&fMenuBar); 815 QMetaObject::connectSlotsByName(SkDebuggerGUI); 816} 817 818void SkDebuggerGUI::setupDirectoryWidget() { 819 QDir dir(fPath); 820 QRegExp r(".skp"); 821 fDirectoryWidget.clear(); 822 const QStringList files = dir.entryList(); 823 foreach (QString f, files) { 824 if (f.contains(r)) 825 fDirectoryWidget.addItem(f); 826 } 827} 828 829// SkOffsetPicturePlayback records the offset of each command in the picture. 830// These are needed by the profiling system. 831class SkOffsetPicturePlayback : public SkPicturePlayback { 832public: 833 SkOffsetPicturePlayback(SkStream* stream, const SkPictInfo& info, bool* isValid, 834 SkSerializationHelpers::DecodeBitmap decoder) 835 : INHERITED(stream, info, isValid, decoder) { 836 } 837 838 const SkTDArray<size_t>& offsets() const { return fOffsets; } 839 840protected: 841 SkTDArray<size_t> fOffsets; 842 843 virtual void preDraw(size_t offset, int type) { 844 *fOffsets.append() = offset; 845 } 846 847private: 848 typedef SkPicturePlayback INHERITED; 849}; 850 851// Picture to wrap an SkOffsetPicturePlayback. 852class SkOffsetPicture : public SkPicture { 853public: 854 SkOffsetPicture(SkStream* stream, 855 bool* success, 856 SkSerializationHelpers::DecodeBitmap decoder) { 857 if (success) { 858 *success = false; 859 } 860 fRecord = NULL; 861 fPlayback = NULL; 862 fWidth = fHeight = 0; 863 864 SkPictInfo info; 865 866 if (!stream->read(&info, sizeof(info))) { 867 return; 868 } 869 if (PICTURE_VERSION != info.fVersion) { 870 return; 871 } 872 873 if (stream->readBool()) { 874 bool isValid = false; 875 fPlayback = SkNEW_ARGS(SkOffsetPicturePlayback, (stream, info, &isValid, decoder)); 876 if (!isValid) { 877 SkDELETE(fPlayback); 878 fPlayback = NULL; 879 return; 880 } 881 } 882 883 // do this at the end, so that they will be zero if we hit an error. 884 fWidth = info.fWidth; 885 fHeight = info.fHeight; 886 if (success) { 887 *success = true; 888 } 889 } 890 891 const SkTDArray<size_t>& offsets() const { 892 return ((SkOffsetPicturePlayback*) fPlayback)->offsets(); 893 } 894 895private: 896 // disallow default ctor b.c. we don't have a good way to setup the fPlayback ptr 897 SkOffsetPicture(); 898 // disallow the copy ctor - enabling would require copying code from SkPicture 899 SkOffsetPicture(const SkOffsetPicture& src); 900 901 typedef SkPicture INHERITED; 902}; 903 904 905 906void SkDebuggerGUI::loadPicture(const SkString& fileName) { 907 fFileName = fileName; 908 fLoading = true; 909 SkStream* stream = SkNEW_ARGS(SkFILEStream, (fileName.c_str())); 910 SkOffsetPicture* picture = SkNEW_ARGS(SkOffsetPicture, (stream, NULL, &SkImageDecoder::DecodeStream)); 911 912 fCanvasWidget.resetWidgetTransform(); 913 fDebugger.loadPicture(picture); 914 915 fOffsets = picture->offsets(); 916 917 SkSafeUnref(stream); 918 SkSafeUnref(picture); 919 920 // Will this automatically clear out due to nature of refcnt? 921 SkTArray<SkString>* commands = fDebugger.getDrawCommandsAsStrings(); 922 923 // If SkPicturePlayback is compiled w/o SK_PICTURE_PROFILING_STUBS 924 // the offset count will always be zero 925 SkASSERT(0 == fOffsets.count() || commands->count() == fOffsets.count()); 926 if (commands->count() == fOffsets.count()) { 927 fActionProfile.setDisabled(false); 928 } 929 930 /* fDebugCanvas is reinitialized every load picture. Need it to retain value 931 * of the visibility filter. 932 * TODO(chudy): This should be deprecated since fDebugger is not 933 * recreated. 934 * */ 935 fDebugger.highlightCurrentCommand(fSettingsWidget.getVisibilityButton()->isChecked()); 936 937 setupListWidget(commands); 938 setupComboBox(commands); 939 setupOverviewText(NULL, 0.0); 940 fInspectorWidget.setDisabled(false); 941 fSettingsWidget.setDisabled(false); 942 fMenuEdit.setDisabled(false); 943 fMenuNavigate.setDisabled(false); 944 fMenuView.setDisabled(false); 945 fActionSave.setDisabled(false); 946 fActionSaveAs.setDisabled(false); 947 fLoading = false; 948 actionPlay(); 949} 950 951void SkDebuggerGUI::setupListWidget(SkTArray<SkString>* command) { 952 fListWidget.clear(); 953 int counter = 0; 954 int indent = 0; 955 for (int i = 0; i < command->count(); i++) { 956 QListWidgetItem *item = new QListWidgetItem(); 957 item->setData(Qt::DisplayRole, (*command)[i].c_str()); 958 item->setData(Qt::UserRole + 1, counter++); 959 960 if (0 == strcmp("Restore", (*command)[i].c_str())) { 961 indent -= 10; 962 } 963 964 item->setData(Qt::UserRole + 3, indent); 965 966 if (0 == strcmp("Save", (*command)[i].c_str()) || 967 0 == strcmp("Save Layer", (*command)[i].c_str())) { 968 indent += 10; 969 } 970 971 item->setData(Qt::UserRole + 4, -1.0); 972 973 fListWidget.addItem(item); 974 } 975} 976 977void SkDebuggerGUI::setupOverviewText(const SkTDArray<double>* typeTimes, double totTime) { 978 979 const SkTDArray<SkDrawCommand*>& commands = fDebugger.getDrawCommands(); 980 981 SkTDArray<int> counts; 982 counts.setCount(LAST_DRAWTYPE_ENUM+1); 983 for (int i = 0; i < LAST_DRAWTYPE_ENUM+1; ++i) { 984 counts[i] = 0; 985 } 986 987 for (int i = 0; i < commands.count(); i++) { 988 counts[commands[i]->getType()]++; 989 } 990 991 QString overview; 992 int total = 0; 993#ifdef SK_DEBUG 994 double totPercent = 0, tempSum = 0; 995#endif 996 for (int i = 0; i < LAST_DRAWTYPE_ENUM+1; ++i) { 997 if (0 == counts[i]) { 998 // if there were no commands of this type then they should've consumed no time 999 SkASSERT(NULL == typeTimes || 0.0 == (*typeTimes)[i]); 1000 continue; 1001 } 1002 1003 overview.append(SkDrawCommand::GetCommandString((DrawType) i)); 1004 overview.append(": "); 1005 overview.append(QString::number(counts[i])); 1006 if (NULL != typeTimes) { 1007 overview.append(" - "); 1008 overview.append(QString::number((*typeTimes)[i], 'f', 1)); 1009 overview.append("ms"); 1010 overview.append(" - "); 1011 double percent = 100.0*(*typeTimes)[i]/totTime; 1012 overview.append(QString::number(percent, 'f', 1)); 1013 overview.append("%"); 1014#ifdef SK_DEBUG 1015 totPercent += percent; 1016 tempSum += (*typeTimes)[i]; 1017#endif 1018 } 1019 overview.append("<br/>"); 1020 total += counts[i]; 1021 } 1022#ifdef SK_DEBUG 1023 if (NULL != typeTimes) { 1024 SkASSERT(SkScalarNearlyEqual(totPercent, 100.0)); 1025 SkASSERT(SkScalarNearlyEqual(tempSum, totTime)); 1026 } 1027#endif 1028 1029 if (totTime > 0.0) { 1030 overview.append("Total Time: "); 1031 overview.append(QString::number(totTime, 'f', 2)); 1032 overview.append("ms"); 1033#ifdef SK_DEBUG 1034 overview.append(" "); 1035 overview.append(QString::number(totPercent)); 1036 overview.append("% "); 1037#endif 1038 overview.append("<br/>"); 1039 } 1040 1041 QString totalStr; 1042 totalStr.append("Total Draw Commands: "); 1043 totalStr.append(QString::number(total)); 1044 totalStr.append("<br/>"); 1045 overview.insert(0, totalStr); 1046 1047 overview.append("<br/>"); 1048 overview.append("SkPicture Width: "); 1049 // NOTE(chudy): This is where we can pull out the SkPictures width. 1050 overview.append(QString::number(fDebugger.pictureWidth())); 1051 overview.append("px<br/>"); 1052 overview.append("SkPicture Height: "); 1053 overview.append(QString::number(fDebugger.pictureHeight())); 1054 overview.append("px"); 1055 fInspectorWidget.setText(overview, SkInspectorWidget::kOverview_TabType); 1056} 1057 1058void SkDebuggerGUI::setupComboBox(SkTArray<SkString>* command) { 1059 fFilter.clear(); 1060 fFilter.addItem("--Filter By Available Commands--"); 1061 1062 std::map<std::string, int> map; 1063 for (int i = 0; i < command->count(); i++) { 1064 map[(*command)[i].c_str()]++; 1065 } 1066 1067 for (std::map<std::string, int>::iterator it = map.begin(); it != map.end(); 1068 ++it) { 1069 fFilter.addItem((it->first).c_str()); 1070 } 1071 1072 // NOTE(chudy): Makes first item unselectable. 1073 QStandardItemModel* model = qobject_cast<QStandardItemModel*>( 1074 fFilter.model()); 1075 QModelIndex firstIndex = model->index(0, fFilter.modelColumn(), 1076 fFilter.rootModelIndex()); 1077 QStandardItem* firstItem = model->itemFromIndex(firstIndex); 1078 firstItem->setSelectable(false); 1079} 1080