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