Avocado/src/platform/windows/gui/debug/gpu.cpp
Jakub Czekański fb7997fc16 externals: Update submodules
gui: fixes
2019-12-05 22:35:34 +01:00

434 lines
17 KiB
C++

#include "gpu.h"
#define _USE_MATH_DEFINES
#include <fmt/core.h>
#include <imgui.h>
#include <math.h>
#include <magic_enum.hpp>
#include <nlohmann/json.hpp>
#include "config.h"
#include "platform/windows/gui/images.h"
#include "renderer/opengl/opengl.h"
#include "system.h"
#include "utils/file.h"
namespace gui::debug {
GPU::GPU() {
busToken = bus.listen<Event::Config::Graphics>([&](auto) {
textureImage.release();
vramImage.release();
});
}
GPU::~GPU() { bus.unlistenAll(busToken); }
void GPU::registersWindow(System *sys) {
auto &gpu = sys->gpu;
ImGui::SetNextWindowSize(ImVec2(200, 100), ImGuiCond_FirstUseEver);
ImGui::Begin("GPU", &registersWindowOpen);
int horRes = gpu->gp1_08.getHorizontalResoulution();
int verRes = gpu->gp1_08.getVerticalResoulution();
bool interlaced = gpu->gp1_08.interlace;
int mode = gpu->gp1_08.videoMode == gpu::GP1_08::VideoMode::ntsc ? 60 : 50;
int colorDepth = gpu->gp1_08.colorDepth == gpu::GP1_08::ColorDepth::bit24 ? 24 : 15;
ImGui::Text("Display:");
ImGui::Text("Resolution %dx%d%c @ %dHz", horRes, verRes, interlaced ? 'i' : 'p', mode);
ImGui::Text("Color depth: %dbit", colorDepth);
ImGui::Text("areaStart: %4d:%4d", gpu->displayAreaStartX, gpu->displayAreaStartY);
ImGui::Text("rangeX: %4d:%4d", gpu->displayRangeX1, gpu->displayRangeX2);
ImGui::Text("rangeY: %4d:%4d", gpu->displayRangeY1, gpu->displayRangeY2);
ImGui::Text("");
ImGui::Text("Drawing:");
ImGui::Text("areaMin: %4d:%4d", gpu->drawingArea.left, gpu->drawingArea.top);
ImGui::Text("areaMax: %4d:%4d", gpu->drawingArea.right, gpu->drawingArea.bottom);
ImGui::Text("offset: %4d:%4d", gpu->drawingOffsetX, gpu->drawingOffsetY);
// ImGui::Text("")
ImGui::End();
}
void replayCommands(gpu::GPU *gpu, int to) {
using gpu::Command;
auto commands = gpu->gpuLogList;
gpu->vram = gpu->prevVram;
gpu->gpuLogEnabled = false;
for (int i = 0; i <= to; i++) {
auto cmd = commands.at(i);
if (cmd.args.size() == 0) fmt::print("Panic! no args");
for (size_t j = 0; j < cmd.args.size(); j++) {
uint32_t arg = cmd.args[j];
if (j == 0) arg |= cmd.command << 24;
if (j == 0 && i == to && (cmd.cmd == Command::Polygon || cmd.cmd == Command::Rectangle || cmd.cmd == Command::Line)) {
arg = (cmd.command << 24) | 0x00ff00;
if (cmd.cmd == Command::Polygon || cmd.cmd == Command::Rectangle) arg &= ~(1 << 24);
}
gpu->write(0, arg);
}
}
gpu->gpuLogEnabled = true;
}
void GPU::logWindow(System *sys) {
vramAreas.clear();
if (!textureImage) {
textureImage = std::make_unique<Texture>(512, 512, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, false);
textureUnpacked.resize(512 * 512 * 4);
}
ImGui::SetNextWindowSize(ImVec2(300, 400), ImGuiCond_FirstUseEver);
ImGui::Begin("GPU Log", &logWindowOpen);
ImGui::BeginChild("GPU Log", ImVec2(0, -ImGui::GetItemsLineHeightWithSpacing()), false);
ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
// TODO: Find last values
gpu::GP0_E1 last_e1 = sys->gpu->gp0_e1;
int16_t last_offset_x = sys->gpu->drawingOffsetX;
int16_t last_offset_y = sys->gpu->drawingOffsetY;
int renderTo = -1;
ImGuiListClipper clipper((int)sys->gpu.get()->gpuLogList.size());
while (clipper.Step()) {
for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) {
auto &entry = sys->gpu.get()->gpuLogList[i];
bool nodeOpen = ImGui::TreeNode((void *)(intptr_t)i, "%4d cmd: 0x%02x %s", i, entry.command,
std::string(magic_enum::enum_name(entry.cmd)).c_str());
bool isHovered = ImGui::IsItemHovered();
if (isHovered) {
renderTo = i;
}
// Analyze
if (isHovered || nodeOpen) {
bool showArguments = true;
float color[3];
color[0] = (entry.args[0] & 0xff) / 255.f;
color[1] = ((entry.args[0] >> 8) & 0xff) / 255.f;
color[2] = ((entry.args[0] >> 16) & 0xff) / 255.f;
uint8_t command = entry.command;
auto arguments = entry.args;
if (command >= 0x20 && command < 0x40) {
auto arg = gpu::PolygonArgs(command);
int ptr = 1;
primitive::Triangle::Vertex v[4];
gpu::TextureInfo tex;
for (int i = 0; i < arg.getVertexCount(); i++) {
v[i].pos.x = extend_sign<11>(arguments[ptr] & 0xffff);
v[i].pos.y = extend_sign<11>((arguments[ptr++] & 0xffff0000) >> 16);
if (!arg.isRawTexture && (!arg.gouroudShading || i == 0)) v[i].color.raw = arguments[0] & 0xffffff;
if (arg.isTextureMapped) {
if (i == 0) tex.palette = arguments[ptr];
if (i == 1) tex.texpage = arguments[ptr];
v[i].uv.x = arguments[ptr] & 0xff;
v[i].uv.y = (arguments[ptr] >> 8) & 0xff;
ptr++;
}
if (arg.gouroudShading && i < arg.getVertexCount() - 1) v[i + 1].color.raw = arguments[ptr++] & 0xffffff;
}
if (nodeOpen) {
std::string flags;
if (arg.semiTransparency) flags += "semi-transparent, "; // TODO: print WHICH transperancy is used, magic enum
if (arg.isTextureMapped) flags += "textured, "; // TODO: Bits?
if (!arg.isRawTexture) flags += "color-blended, ";
if (arg.gouroudShading) flags += "gouroud-shaded";
ImGui::Text("Flags: %s", flags.c_str());
for (int i = 0; i < arg.getVertexCount(); i++) {
auto text = fmt::format("v{}: {}x{}", i, v[i].pos.x + last_offset_x, v[i].pos.y + last_offset_y);
if (arg.isTextureMapped) {
text += fmt::format(", uv{}: {}x{}", i, v[i].uv.x, v[i].uv.y);
}
ImGui::TextUnformatted(text.c_str());
}
ImGui::NewLine();
ImGui::Text("Color: ");
ImGui::SameLine();
ImGui::ColorEdit3("##color", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoInputs);
}
// vramAreas.push_back({fmt::format("Rectangle {}", i), ImVec2(x, y), ImVec2(w, h)});
if (arg.isTextureMapped) {
// int clutColors = tex.getBitcount();
// int textureWidth;
// int textureBits;
// if (last_e1.texturePageColors == gpu::GP0_E1::TexturePageColors::bit4) {
// clutColors = 16;
// textureWidth = w / 4;
// textureBits = 4;
// } else if (last_e1.texturePageColors == gpu::GP0_E1::TexturePageColors::bit8) {
// clutColors = 256;
// textureWidth = w / 2;
// textureBits = 8;
// } else {
// clutColors = 0;
// textureWidth = w;
// textureBits = 16;
// }
std::string textureInfo = fmt::format("Texture ({} bit)", tex.getBitcount());
if (nodeOpen) {
ImGui::NewLine();
ImGui::Text("%s: ", textureInfo.c_str());
ImGui::Text("texPage: %dx%d", tex.getBaseX(), tex.getBaseY());
ImGui::Text("CLUT: %dx%d", tex.getClutX(), tex.getClutY());
}
// vramAreas.push_back({textureInfo, ImVec2(texX, texY), ImVec2(textureWidth, h)});
if (tex.getBitcount() != 0) {
vramAreas.push_back({"CLUT", ImVec2(tex.getClutX(), tex.getClutY()), ImVec2(tex.getBitcount(), 1)});
}
}
showArguments = false;
} else if (command >= 0x40 && command < 0x60) {
auto arg = gpu::LineArgs(command);
(void)arg; // TODO: Parse Line commands
} else if (command >= 0x60 && command < 0x80) {
auto arg = gpu::RectangleArgs(command);
int16_t w = arg.getSize();
int16_t h = arg.getSize();
if (arg.size == 0) {
w = extend_sign<11>(arguments[(arg.isTextureMapped ? 3 : 2)] & 0xffff);
h = extend_sign<11>((arguments[(arg.isTextureMapped ? 3 : 2)] & 0xffff0000) >> 16);
}
int16_t x = extend_sign<11>(arguments[1] & 0xffff);
int16_t y = extend_sign<11>((arguments[1] & 0xffff0000) >> 16);
x += last_offset_x;
y += last_offset_y;
if (nodeOpen) {
std::string flags;
if (arg.semiTransparency) flags += "semi-transparent, ";
if (arg.isTextureMapped) flags += "textured, ";
if (!arg.isRawTexture) flags += "color-blended, ";
ImGui::Text("Flags: %s", flags.c_str());
ImGui::Text("Pos: %dx%d", x, y);
ImGui::Text("size: %dx%d", w, h);
ImGui::NewLine();
ImGui::Text("Color: ");
ImGui::SameLine();
ImGui::ColorEdit3("##color", color, ImGuiColorEditFlags_NoPicker | ImGuiColorEditFlags_NoInputs);
}
vramAreas.push_back({fmt::format("Rectangle {}", i), ImVec2(x, y), ImVec2(w, h)});
if (arg.isTextureMapped) {
int texX = arguments[2] & 0xff;
int texY = (arguments[2] & 0xff00) >> 8;
int clutX = ((arguments[2] >> 16) & 0x3f) * 16;
int clutY = ((arguments[2] >> 22) & 0x1ff);
int clutColors;
int textureWidth;
int textureBits;
if (last_e1.texturePageColors == gpu::GP0_E1::TexturePageColors::bit4) {
clutColors = 16;
textureWidth = w / 4;
textureBits = 4;
} else if (last_e1.texturePageColors == gpu::GP0_E1::TexturePageColors::bit8) {
clutColors = 256;
textureWidth = w / 2;
textureBits = 8;
} else {
clutColors = 0;
textureWidth = w;
textureBits = 16;
}
int texPageX = last_e1.texturePageBaseX * 64;
int texPageY = last_e1.texturePageBaseY * 256;
std::string textureInfo = fmt::format("Texture ({} bit)", textureBits);
if (nodeOpen) {
ImGui::NewLine();
ImGui::Text("%s: ", textureInfo.c_str());
ImGui::Text("UV: %dx%d", texX + texPageX, texY + texPageY);
ImGui::Text("CLUT: %dx%d", clutX, clutY);
ImGui::NewLine();
ImGui::Text("Pos: %dx%d (raw value in draw call)", texX, texY);
ImGui::Text("texPage: %dx%d (from latest GP0_E1)", texPageX, texPageY);
// ImGui::SameLine();
// ImGui::Image()
// TODO: Render texture
}
vramAreas.push_back(
{textureInfo, ImVec2(texPageX + texX / (16 / textureBits), texPageY + texY), ImVec2(textureWidth, h)});
if (clutColors != 0) {
vramAreas.push_back({"CLUT", ImVec2(clutX, clutY), ImVec2(clutColors, 1)});
}
}
showArguments = false;
}
if (nodeOpen) {
if (showArguments) {
// Render arguments
for (auto &arg : entry.args) {
ImGui::Text("- 0x%08x", arg);
}
}
ImGui::TreePop();
}
}
}
}
ImGui::PopStyleVar();
ImGui::EndChild();
if (ImGui::Button("Dump")) {
ImGui::OpenPopup("Save dump dialog");
}
if (ImGui::BeginPopupModal("Save dump dialog", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
char filename[32];
ImGui::Text("File: ");
ImGui::SameLine();
ImGui::PushItemWidth(140);
if (ImGui::InputText("", filename, 31, ImGuiInputTextFlags_EnterReturnsTrue)) {
auto gpu = sys->gpu.get();
auto gpuLog = gpu->gpuLogList;
nlohmann::json j;
for (size_t i = 0; i < gpuLog.size(); i++) {
auto e = gpuLog[i];
j.push_back({
{"command", e.command}, //
{"cmd", (int)e.cmd}, //
{"name", magic_enum::enum_name(e.cmd)}, //
{"args", e.args}, //
});
}
putFileContents(fmt::format("{}.json", filename), j.dump(2));
// Binary vram dump
std::vector<uint8_t> vram;
for (auto d : gpu->vram) {
vram.push_back(d & 0xff);
vram.push_back((d >> 8) & 0xff);
}
putFileContents(fmt::format("{}.bin", filename), vram);
ImGui::CloseCurrentPopup();
}
ImGui::PopItemWidth();
ImGui::SameLine();
if (ImGui::Button("Close")) {
ImGui::CloseCurrentPopup();
}
ImGui::Text("(press Enter to add)");
ImGui::EndPopup();
}
ImGui::End();
if (sys->state != System::State::run && renderTo >= 0) {
replayCommands(sys->gpu.get(), renderTo);
}
}
void GPU::vramWindow(gpu::GPU *gpu) {
if (!vramImage) {
vramImage = std::make_unique<Texture>(gpu::VRAM_WIDTH, gpu::VRAM_HEIGHT, GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, false);
vramUnpacked.resize(gpu::VRAM_WIDTH * gpu::VRAM_HEIGHT * 3);
}
// Update texture
for (int y = 0; y < gpu::VRAM_HEIGHT; y++) {
for (int x = 0; x < gpu::VRAM_WIDTH; x++) {
PSXColor c = gpu->vram[y * gpu::VRAM_WIDTH + x];
vramUnpacked[(y * gpu::VRAM_WIDTH + x) * 3 + 0] = c.r << 3;
vramUnpacked[(y * gpu::VRAM_WIDTH + x) * 3 + 1] = c.g << 3;
vramUnpacked[(y * gpu::VRAM_WIDTH + x) * 3 + 2] = c.b << 3;
}
}
vramImage->update(vramUnpacked.data());
blinkTimer += 0.0025 * M_PI;
ImColor blinkColor = ImColor::HSV(blinkTimer, 1.f, 1.f, 0.75f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0.0, 0.0, 0.0, 1.0));
auto defaultSize = ImVec2(1024, 512 + ImGui::GetItemsLineHeightWithSpacing() * 2);
ImGui::SetNextWindowSizeConstraints(
ImVec2(defaultSize.x / 2, defaultSize.y / 2), ImVec2(defaultSize.x * 2, defaultSize.y * 2),
[](ImGuiSizeCallbackData *data) { data->DesiredSize.y = (data->DesiredSize.x / 2) + ImGui::GetItemsLineHeightWithSpacing() * 2; });
ImGui::SetNextWindowSize(defaultSize);
ImGui::Begin("VRAM", &vramWindowOpen, ImGuiWindowFlags_NoScrollbar);
auto currentSize = ImGui::GetWindowContentRegionMax();
currentSize.y = currentSize.x / 2;
ImVec2 cursorPos = ImGui::GetCursorScreenPos();
ImGui::Image((ImTextureID)(uintptr_t)vramImage->get(), currentSize);
if (ImGui::Button("Original size")) {
ImGui::SetWindowSize(defaultSize);
}
ImGui::PopStyleColor();
ImGui::PopStyleVar();
float scale = currentSize.x / defaultSize.x;
ImDrawList *drawList = ImGui::GetWindowDrawList();
for (auto area : vramAreas) {
ImVec2 a, b;
a.x = cursorPos.x + area.pos.x * scale;
a.y = cursorPos.y + area.pos.y * scale;
b.x = cursorPos.x + (area.pos.x + area.size.x) * scale;
b.y = cursorPos.y + (area.pos.y + area.size.y) * scale;
drawList->AddRectFilled(a, b, blinkColor, 0.f, 0);
if (ImGui::IsMouseHoveringRect(a, b)) {
ImGui::BeginTooltip();
ImGui::TextUnformatted(area.name.c_str());
ImGui::EndTooltip();
}
}
ImGui::End();
}
void GPU::displayWindows(System *sys) {
if (registersWindowOpen) registersWindow(sys);
if (logWindowOpen) logWindow(sys);
if (vramWindowOpen) vramWindow(sys->gpu.get());
}
}; // namespace gui::debug