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