skia2/tools/viewer/SkSLDebuggerSlide.cpp
John Stiles d67f1f8f50 Provide bit-casted slot values from DebugTracePlayer.
Previously, the VariableData returned from a DebugTracePlayer contained
values in an int32_t, regardless of the slot's NumberKind. We had a
helper function which could stringize the bits, but otherwise the caller
was responsible for bit-casting the value manually.

Now, the DebugTracePlayer will automatically manage bit-casting for the
caller. The value returned in the VariableData is now a double (so it
is able to store an int32, uint32, or float at full precision).

This change was inspired by the recent Typescript port. (The value in
the Typescript VariableData uses a compound `number | boolean` type, so
it is able to fully represent any slot value natively.)

Change-Id: I5eec414236f76ad0ff51b0b19974e4a0025c4d62
Bug: skia:12666
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/489896
Auto-Submit: John Stiles <johnstiles@google.com>
Reviewed-by: Brian Osman <brianosman@google.com>
Commit-Queue: Brian Osman <brianosman@google.com>
2021-12-29 18:30:19 +00:00

285 lines
11 KiB
C++

/*
* Copyright 2021 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "tools/viewer/SkSLDebuggerSlide.h"
#include "include/core/SkCanvas.h"
#include "tools/viewer/Viewer.h"
#include <algorithm>
#include <cstdio>
#include "imgui.h"
using namespace sk_app;
using LineNumberMap = SkSL::SkVMDebugTracePlayer::LineNumberMap;
///////////////////////////////////////////////////////////////////////////////
SkSLDebuggerSlide::SkSLDebuggerSlide() {
fName = "Debugger";
fTrace = sk_make_sp<SkSL::SkVMDebugTrace>();
}
void SkSLDebuggerSlide::load(SkScalar winWidth, SkScalar winHeight) {}
void SkSLDebuggerSlide::unload() {
fTrace = sk_make_sp<SkSL::SkVMDebugTrace>();
fPlayer.reset(nullptr);
fPlayer.setBreakpoints(std::unordered_set<int>{});
}
void SkSLDebuggerSlide::showLoadTraceGUI() {
ImGui::InputText("Trace Path", fTraceFile, SK_ARRAY_COUNT(fTraceFile));
bool load = ImGui::Button("Load Debug Trace");
if (load) {
SkFILEStream file(fTraceFile);
if (!file.isValid()) {
ImGui::OpenPopup("Can't Open Trace");
} else if (!fTrace->readTrace(&file)) {
ImGui::OpenPopup("Invalid Trace");
} else {
// Trace loaded successfully. On the next refresh, the user will see the debug UI.
fPlayer.reset(fTrace);
fPlayer.step();
fRefresh = true;
return;
}
}
if (ImGui::BeginPopupModal("Can't Open Trace", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("The trace file doesn't exist.");
ImGui::Separator();
if (ImGui::Button("OK", ImVec2(120, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
if (ImGui::BeginPopupModal("Invalid Trace", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text("The trace data could not be parsed.");
ImGui::Separator();
if (ImGui::Button("OK", ImVec2(120, 0))) {
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
}
void SkSLDebuggerSlide::showDebuggerGUI() {
if (ImGui::Button("Reset")) {
fPlayer.reset(fTrace);
fRefresh = true;
}
ImGui::SameLine(/*offset_from_start_x=*/0, /*spacing=*/100);
if (ImGui::Button("Step")) {
fPlayer.step();
fRefresh = true;
}
ImGui::SameLine();
if (ImGui::Button("Step Over")) {
fPlayer.stepOver();
fRefresh = true;
}
ImGui::SameLine();
if (ImGui::Button("Step Out")) {
fPlayer.stepOut();
fRefresh = true;
}
ImGui::SameLine(/*offset_from_start_x=*/0, /*spacing=*/100);
if (ImGui::Button(fPlayer.getBreakpoints().empty() ? "Run" : "Run to Breakpoint")) {
fPlayer.run();
fRefresh = true;
}
this->showStackTraceTable();
this->showVariableTable();
this->showCodeTable();
}
void SkSLDebuggerSlide::showCodeTable() {
constexpr ImGuiTableFlags kTableFlags =
ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersV;
constexpr ImGuiTableColumnFlags kColumnFlags =
ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoReorder |
ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_NoSort |
ImGuiTableColumnFlags_NoHeaderLabel;
ImVec2 contentRect = ImGui::GetContentRegionAvail();
ImVec2 codeViewSize = ImVec2(0.0f, contentRect.y);
if (ImGui::BeginTable("Code View", /*column=*/2, kTableFlags, codeViewSize)) {
ImGui::TableSetupColumn("", kColumnFlags | ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("Code", kColumnFlags | ImGuiTableColumnFlags_WidthStretch);
ImGuiListClipper clipper;
clipper.Begin(fTrace->fSource.size());
while (clipper.Step()) {
for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) {
size_t humanReadableLine = row + 1;
ImGui::TableNextRow();
if (fPlayer.getCurrentLine() == (int)humanReadableLine) {
ImGui::TableSetBgColor(
ImGuiTableBgTarget_RowBg1,
ImGui::GetColorU32(ImGui::GetStyleColorVec4(ImGuiCol_TextSelectedBg)));
}
// Show line numbers and breakpoints.
ImGui::TableSetColumnIndex(0);
const LineNumberMap& lineNumberMap = fPlayer.getLineNumbersReached();
LineNumberMap::const_iterator iter = lineNumberMap.find(humanReadableLine);
bool reachable = iter != lineNumberMap.end() && iter->second > 0;
bool breakpointOn = fPlayer.getBreakpoints().count(humanReadableLine);
if (breakpointOn) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 0.0f, 0.0f, 0.70f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0.0f, 0.0f, 0.85f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
} else if (reachable) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 0.75f));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.2f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.4f));
} else {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 1.0f, 1.0f, 0.25f));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
}
if (ImGui::SmallButton(SkStringPrintf("%03zu ", humanReadableLine).c_str())) {
if (breakpointOn) {
fPlayer.removeBreakpoint(humanReadableLine);
} else if (reachable) {
fPlayer.addBreakpoint(humanReadableLine);
}
}
ImGui::PopStyleColor(4);
// Show lines of code.
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s", fTrace->fSource[row].c_str());
}
}
if (fRefresh) {
int linesVisible = contentRect.y / ImGui::GetTextLineHeightWithSpacing();
int centerLine = (fPlayer.getCurrentLine() - 1) - (linesVisible / 2);
centerLine = std::max(0, centerLine);
ImGui::SetScrollY(clipper.ItemsHeight * centerLine);
fRefresh = false;
}
ImGui::EndTable();
}
}
void SkSLDebuggerSlide::showStackTraceTable() {
constexpr ImGuiTableFlags kTableFlags =
ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersV | ImGuiTableFlags_NoHostExtendX;
constexpr ImGuiTableColumnFlags kColumnFlags =
ImGuiTableColumnFlags_NoReorder | ImGuiTableColumnFlags_NoHide |
ImGuiTableColumnFlags_NoSort;
std::vector<int> callStack = fPlayer.getCallStack();
ImVec2 contentRect = ImGui::GetContentRegionAvail();
ImVec2 stackViewSize = ImVec2(contentRect.x / 3.0f,
ImGui::GetTextLineHeightWithSpacing() * kNumTopRows);
if (ImGui::BeginTable("Call Stack", /*column=*/1, kTableFlags, stackViewSize)) {
ImGui::TableSetupColumn("Stack", kColumnFlags);
ImGui::TableHeadersRow();
ImGuiListClipper clipper;
clipper.Begin(callStack.size());
while (clipper.Step()) {
for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) {
int funcIdx = callStack.rbegin()[row];
SkASSERT(funcIdx >= 0 && (size_t)funcIdx < fTrace->fFuncInfo.size());
const SkSL::SkVMFunctionInfo& funcInfo = fTrace->fFuncInfo[funcIdx];
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::Text("%s", funcInfo.name.c_str());
}
}
ImGui::EndTable();
}
ImGui::SameLine();
}
void SkSLDebuggerSlide::showVariableTable() {
constexpr ImGuiTableFlags kTableFlags =
ImGuiTableFlags_ScrollY | ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersOuter |
ImGuiTableFlags_BordersV | ImGuiTableFlags_Resizable;
constexpr ImGuiTableColumnFlags kColumnFlags =
ImGuiTableColumnFlags_NoReorder | ImGuiTableColumnFlags_NoHide |
ImGuiTableColumnFlags_NoSort | ImGuiTableColumnFlags_WidthStretch;
int frame = fPlayer.getStackDepth() - 1;
std::vector<SkSL::SkVMDebugTracePlayer::VariableData> vars;
if (frame >= 0) {
vars = fPlayer.getLocalVariables(frame);
} else {
vars = fPlayer.getGlobalVariables();
}
ImVec2 varViewSize = ImVec2(0.0f, ImGui::GetTextLineHeightWithSpacing() * kNumTopRows);
if (ImGui::BeginTable("Variables", /*column=*/2, kTableFlags, varViewSize)) {
ImGui::TableSetupColumn("Variable", kColumnFlags);
ImGui::TableSetupColumn("Value", kColumnFlags);
ImGui::TableHeadersRow();
if (!vars.empty()) {
ImGuiListClipper clipper;
clipper.Begin(vars.size());
while (clipper.Step()) {
for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; row++) {
const SkSL::SkVMDebugTracePlayer::VariableData& var = vars.at(row);
SkASSERT(var.fSlotIndex >= 0);
SkASSERT((size_t)var.fSlotIndex < fTrace->fSlotInfo.size());
const SkSL::SkVMSlotInfo& slotInfo = fTrace->fSlotInfo[var.fSlotIndex];
ImGui::TableNextRow();
if (var.fDirty) {
// Highlight recently-changed variables.
ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg1,
ImGui::GetColorU32(ImVec4{0.0f, 1.0f, 0.0f, 0.20f}));
}
ImGui::TableSetColumnIndex(0);
ImGui::Text("%s%s", slotInfo.name.c_str(),
fTrace->getSlotComponentSuffix(var.fSlotIndex).c_str());
ImGui::TableSetColumnIndex(1);
ImGui::Text("%s",
fTrace->slotValueToString(var.fSlotIndex, var.fValue).c_str());
}
}
}
ImGui::EndTable();
}
}
void SkSLDebuggerSlide::showRootGUI() {
if (fTrace->fSource.empty()) {
this->showLoadTraceGUI();
return;
}
this->showDebuggerGUI();
}
void SkSLDebuggerSlide::draw(SkCanvas* canvas) {
canvas->clear(SK_ColorWHITE);
ImGui::Begin("Debugger", nullptr, ImGuiWindowFlags_AlwaysVerticalScrollbar);
this->showRootGUI();
ImGui::End();
}
bool SkSLDebuggerSlide::animate(double nanos) {
return true;
}