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