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