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