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