• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 Google LLC
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 "tools/viewer/SkSLDebuggerSlide.h"
9 
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkColor.h"
12 #include "include/core/SkStream.h"
13 #include "include/core/SkString.h"
14 #include "include/private/base/SkAssert.h"
15 #include "tools/sk_app/Application.h"
16 #include "tools/sksltrace/SkSLTraceUtils.h"
17 
18 #include <algorithm>
19 #include <cstdio>
20 #include <string>
21 #include <unordered_map>
22 #include <unordered_set>
23 #include <utility>
24 #include <vector>
25 
26 #include "imgui.h"
27 
28 using namespace sk_app;
29 using LineNumberMap = SkSL::SkSLDebugTracePlayer::LineNumberMap;
30 
SkSLDebuggerSlide()31 SkSLDebuggerSlide::SkSLDebuggerSlide() {
32     fName = "Debugger";
33     fTrace = sk_make_sp<SkSL::DebugTracePriv>();
34 }
35 
load(SkScalar winWidth,SkScalar winHeight)36 void SkSLDebuggerSlide::load(SkScalar winWidth, SkScalar winHeight) {}
37 
unload()38 void SkSLDebuggerSlide::unload() {
39     fTrace = sk_make_sp<SkSL::DebugTracePriv>();
40     fPlayer.reset(nullptr);
41     fPlayer.setBreakpoints(std::unordered_set<int>{});
42 }
43 
showLoadTraceGUI()44 void SkSLDebuggerSlide::showLoadTraceGUI() {
45     ImGui::InputText("Trace Path", fTraceFile, std::size(fTraceFile));
46     bool load = ImGui::Button("Load Debug Trace");
47 
48     if (load) {
49         SkFILEStream file(fTraceFile);
50         if (!file.isValid()) {
51             ImGui::OpenPopup("Can't Open Trace");
52         } else {
53             fTrace = SkSLTraceUtils::ReadTrace(&file);
54             if (!fTrace) {
55                 ImGui::OpenPopup("Invalid Trace");
56             } else {
57                 // Trace loaded successfully. On the next refresh, the user will see the debug UI.
58                 fPlayer.reset(fTrace);
59                 fPlayer.step();
60                 fRefresh = true;
61                 return;
62             }
63         }
64     }
65 
66     if (ImGui::BeginPopupModal("Can't Open Trace", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
67         ImGui::Text("The trace file doesn't exist.");
68         ImGui::Separator();
69         if (ImGui::Button("OK", ImVec2(120, 0))) {
70             ImGui::CloseCurrentPopup();
71         }
72         ImGui::EndPopup();
73     }
74 
75     if (ImGui::BeginPopupModal("Invalid Trace", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
76         ImGui::Text("The trace data could not be parsed.");
77         ImGui::Separator();
78         if (ImGui::Button("OK", ImVec2(120, 0))) {
79             ImGui::CloseCurrentPopup();
80         }
81         ImGui::EndPopup();
82     }
83 }
84 
showDebuggerGUI()85 void SkSLDebuggerSlide::showDebuggerGUI() {
86     if (ImGui::Button("Reset")) {
87         fPlayer.reset(fTrace);
88         fRefresh = true;
89     }
90     ImGui::SameLine(/*offset_from_start_x=*/0, /*spacing=*/100);
91     if (ImGui::Button("Step")) {
92         fPlayer.step();
93         fRefresh = true;
94     }
95     ImGui::SameLine();
96     if (ImGui::Button("Step Over")) {
97         fPlayer.stepOver();
98         fRefresh = true;
99     }
100     ImGui::SameLine();
101     if (ImGui::Button("Step Out")) {
102         fPlayer.stepOut();
103         fRefresh = true;
104     }
105     ImGui::SameLine(/*offset_from_start_x=*/0, /*spacing=*/100);
106     if (ImGui::Button(fPlayer.getBreakpoints().empty() ? "Run" : "Run to Breakpoint")) {
107         fPlayer.run();
108         fRefresh = true;
109     }
110 
111     this->showStackTraceTable();
112     this->showVariableTable();
113     this->showCodeTable();
114 }
115 
showCodeTable()116 void SkSLDebuggerSlide::showCodeTable() {
117     constexpr ImGuiTableFlags kTableFlags =
118             ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter |
119             ImGuiTableFlags_BordersV;
120     constexpr ImGuiTableColumnFlags kColumnFlags =
121             ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoReorder |
122             ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_NoSort |
123             ImGuiTableColumnFlags_NoHeaderLabel;
124 
125     ImVec2 contentRect = ImGui::GetContentRegionAvail();
126     ImVec2 codeViewSize = ImVec2(0.0f, contentRect.y);
127     if (ImGui::BeginTable("Code View", /*column=*/2, kTableFlags, codeViewSize)) {
128         ImGui::TableSetupColumn("", kColumnFlags | ImGuiTableColumnFlags_WidthFixed);
129         ImGui::TableSetupColumn("Code", kColumnFlags | ImGuiTableColumnFlags_WidthStretch);
130 
131         ImGuiListClipper clipper;
132         clipper.Begin(fTrace->fSource.size());
133         while (clipper.Step()) {
134             for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) {
135                 size_t humanReadableLine = row + 1;
136 
137                 ImGui::TableNextRow();
138                 if (fPlayer.getCurrentLine() == (int)humanReadableLine) {
139                     ImGui::TableSetBgColor(
140                             ImGuiTableBgTarget_RowBg1,
141                             ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_TextSelectedBg)));
142                 }
143 
144                 // Show line numbers and breakpoints.
145                 ImGui::TableSetColumnIndex(0);
146                 const LineNumberMap& lineNumberMap = fPlayer.getLineNumbersReached();
147                 LineNumberMap::const_iterator iter = lineNumberMap.find(humanReadableLine);
148                 bool reachable = iter != lineNumberMap.end() && iter->second > 0;
149                 bool breakpointOn = fPlayer.getBreakpoints().count(humanReadableLine);
150                 if (breakpointOn) {
151                     ImGui::PushStyleColor(ImGuiCol_Text,          ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
152                     ImGui::PushStyleColor(ImGuiCol_Button,        ImVec4(1.0f, 0.0f, 0.0f, 0.70f));
153                     ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0.0f, 0.0f, 0.85f));
154                     ImGui::PushStyleColor(ImGuiCol_ButtonActive,  ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
155                 } else if (reachable) {
156                     ImGui::PushStyleColor(ImGuiCol_Text,          ImVec4(1.0f, 1.0f, 1.0f, 0.75f));
157                     ImGui::PushStyleColor(ImGuiCol_Button,        ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
158                     ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f));
159                     ImGui::PushStyleColor(ImGuiCol_ButtonActive,  ImVec4(1.0f, 1.0f, 1.0f, 0.4f));
160                 } else {
161                     ImGui::PushStyleColor(ImGuiCol_Text,          ImVec4(1.0f, 1.0f, 1.0f, 0.25f));
162                     ImGui::PushStyleColor(ImGuiCol_Button,        ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
163                     ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
164                     ImGui::PushStyleColor(ImGuiCol_ButtonActive,  ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
165                 }
166                 if (ImGui::SmallButton(SkStringPrintf("%03zu ", humanReadableLine).c_str())) {
167                     if (breakpointOn) {
168                         fPlayer.removeBreakpoint(humanReadableLine);
169                     } else if (reachable) {
170                         fPlayer.addBreakpoint(humanReadableLine);
171                     }
172                 }
173                 ImGui::PopStyleColor(4);
174 
175                 // Show lines of code.
176                 ImGui::TableSetColumnIndex(1);
177                 ImGui::Text("%s", fTrace->fSource[row].c_str());
178             }
179         }
180 
181         if (fRefresh) {
182             int linesVisible = contentRect.y / ImGui::GetTextLineHeightWithSpacing();
183             int centerLine = (fPlayer.getCurrentLine() - 1) - (linesVisible / 2);
184             centerLine = std::max(0, centerLine);
185             ImGui::SetScrollY(clipper.ItemsHeight * centerLine);
186             fRefresh = false;
187         }
188 
189         ImGui::EndTable();
190     }
191 }
192 
showStackTraceTable()193 void SkSLDebuggerSlide::showStackTraceTable() {
194     constexpr ImGuiTableFlags kTableFlags =
195             ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter |
196             ImGuiTableFlags_BordersV | ImGuiTableFlags_NoHostExtendX;
197     constexpr ImGuiTableColumnFlags kColumnFlags =
198             ImGuiTableColumnFlags_NoReorder | ImGuiTableColumnFlags_NoHide |
199             ImGuiTableColumnFlags_NoSort;
200 
201     std::vector<int> callStack = fPlayer.getCallStack();
202 
203     ImVec2 contentRect = ImGui::GetContentRegionAvail();
204     ImVec2 stackViewSize = ImVec2(contentRect.x / 3.0f,
205                                   ImGui::GetTextLineHeightWithSpacing() * kNumTopRows);
206     if (ImGui::BeginTable("Call Stack", /*column=*/1, kTableFlags, stackViewSize)) {
207         ImGui::TableSetupColumn("Stack", kColumnFlags);
208         ImGui::TableHeadersRow();
209 
210         ImGuiListClipper clipper;
211         clipper.Begin(callStack.size());
212         while (clipper.Step()) {
213             for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) {
214                 int funcIdx = callStack.rbegin()[row];
215                 SkASSERT(funcIdx >= 0 && (size_t)funcIdx < fTrace->fFuncInfo.size());
216                 const SkSL::FunctionDebugInfo& funcInfo = fTrace->fFuncInfo[funcIdx];
217 
218                 ImGui::TableNextRow();
219                 ImGui::TableSetColumnIndex(0);
220                 ImGui::Text("%s", funcInfo.name.c_str());
221             }
222         }
223         ImGui::EndTable();
224     }
225 
226     ImGui::SameLine();
227 }
228 
showVariableTable()229 void SkSLDebuggerSlide::showVariableTable() {
230     constexpr ImGuiTableFlags kTableFlags =
231             ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter |
232             ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable;
233     constexpr ImGuiTableColumnFlags kColumnFlags =
234             ImGuiTableColumnFlags_NoReorder | ImGuiTableColumnFlags_NoHide |
235             ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthStretch;
236 
237     int frame = fPlayer.getStackDepth() - 1;
238     std::vector<SkSL::SkSLDebugTracePlayer::VariableData> vars;
239     if (frame >= 0) {
240         vars = fPlayer.getLocalVariables(frame);
241     } else {
242         vars = fPlayer.getGlobalVariables();
243     }
244     ImVec2 varViewSize = ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * kNumTopRows);
245     if (ImGui::BeginTable("Variables", /*column=*/2, kTableFlags, varViewSize)) {
246         ImGui::TableSetupColumn("Variable", kColumnFlags);
247         ImGui::TableSetupColumn("Value", kColumnFlags);
248         ImGui::TableHeadersRow();
249         if (!vars.empty()) {
250             ImGuiListClipper clipper;
251             clipper.Begin(vars.size());
252             while (clipper.Step()) {
253                 for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) {
254                     const SkSL::SkSLDebugTracePlayer::VariableData& var = vars.at(row);
255                     SkASSERT(var.fSlotIndex >= 0);
256                     SkASSERT((size_t)var.fSlotIndex < fTrace->fSlotInfo.size());
257                     const SkSL::SlotDebugInfo& slotInfo = fTrace->fSlotInfo[var.fSlotIndex];
258 
259                     ImGui::TableNextRow();
260                     if (var.fDirty) {
261                         // Highlight recently-changed variables.
262                         ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1,
263                                                ImGui::GetColorU32(ImVec4{0.0f, 1.0f, 0.0f, 0.20f}));
264                     }
265                     ImGui::TableSetColumnIndex(0);
266                     ImGui::Text("%s%s", slotInfo.name.c_str(),
267                                         fTrace->getSlotComponentSuffix(var.fSlotIndex).c_str());
268                     ImGui::TableSetColumnIndex(1);
269                     ImGui::Text("%s",
270                                 fTrace->slotValueToString(var.fSlotIndex, var.fValue).c_str());
271                 }
272             }
273         }
274         ImGui::EndTable();
275     }
276 }
277 
showRootGUI()278 void SkSLDebuggerSlide::showRootGUI() {
279     if (fTrace->fSource.empty()) {
280         this->showLoadTraceGUI();
281         return;
282     }
283 
284     this->showDebuggerGUI();
285 }
286 
draw(SkCanvas * canvas)287 void SkSLDebuggerSlide::draw(SkCanvas* canvas) {
288     canvas->clear(SK_ColorWHITE);
289     ImGui::Begin("Debugger", nullptr, ImGuiWindowFlags_AlwaysVerticalScrollbar);
290     this->showRootGUI();
291     ImGui::End();
292 }
293 
animate(double nanos)294 bool SkSLDebuggerSlide::animate(double nanos) {
295     return true;
296 }
297