matdbg: refactor EDIT command (#7255)

The websocket code for parsing the EDIT command is pretty verbose.
Proposing that we move to a HTTP POST request instead.

Also moved the API handler code out of DebugServer.h for clarity.
This commit is contained in:
Powei Feng
2023-10-12 10:40:57 -07:00
committed by GitHub
parent 0887e388db
commit 078a17469a
6 changed files with 451 additions and 366 deletions

View File

@@ -16,6 +16,8 @@
#include <matdbg/DebugServer.h>
#include "ApiHandler.h"
#include <CivetServer.h>
#include <utils/FixedCapacityVector.h>
@@ -52,8 +54,7 @@ using utils::FixedCapacityVector;
#include "matdbg_resources.h"
#endif
namespace filament {
namespace matdbg {
namespace filament::matdbg {
using namespace utils;
using namespace filament::backend;
@@ -61,31 +62,24 @@ using namespace filament::backend;
using filaflat::ChunkContainer;
using filamat::ChunkType;
static const std::string_view kSuccessHeader =
std::string_view const DebugServer::kSuccessHeader =
"HTTP/1.1 200 OK\r\nContent-Type: %s\r\n"
"Connection: close\r\n\r\n";
static const std::string_view kErrorHeader =
std::string_view const DebugServer::kErrorHeader =
"HTTP/1.1 404 Not Found\r\nContent-Type: %s\r\n"
"Connection: close\r\n\r\n";
static void spirvToAsm(struct mg_connection *conn, const uint32_t* spirv, size_t size) {
auto spirvDisassembly = ShaderExtractor::spirvToText(spirv, size / 4);
mg_printf(conn, kSuccessHeader.data(), "application/txt");
mg_write(conn, spirvDisassembly.c_str(), spirvDisassembly.size());
}
namespace {
static void spirvToGlsl(ShaderModel shaderModel, struct mg_connection *conn,
const uint32_t* spirv, size_t size) {
auto glsl = ShaderExtractor::spirvToGLSL(shaderModel, spirv, size / 4);
mg_printf(conn, kSuccessHeader.data(), "application/txt");
mg_printf(conn, glsl.c_str(), glsl.size());
}
}// namespace
class FileRequestHandler : public CivetHandler {
public:
FileRequestHandler(DebugServer* server) : mServer(server) {}
bool handleGet(CivetServer *server, struct mg_connection *conn) {
auto const& kSuccessHeader = DebugServer::kSuccessHeader;
const struct mg_request_info* request = mg_get_request_info(conn);
std::string uri(request->request_uri);
if (uri == "/" || uri == "/index.html") {
@@ -122,256 +116,6 @@ private:
DebugServer* mServer;
};
// Handles the following REST requests, where {id} is an 8-digit hex string.
//
// GET /api/matids
// GET /api/materials
// GET /api/material?matid={id}
// GET /api/shader?matid={id}&type=[glsl|spirv]&[glindex|vkindex|metalindex]={index}
// GET /api/active
//
class RestRequestHandler : public CivetHandler {
public:
RestRequestHandler(DebugServer* server) : mServer(server) {}
bool handleGet(CivetServer *server, struct mg_connection *conn) {
const struct mg_request_info* request = mg_get_request_info(conn);
std::string uri(request->local_uri);
const auto error = [request](int line) {
slog.e << "DebugServer: 404 at line " << line << ": " << request->local_uri
<< io::endl;
return false;
};
const auto softError = [request, conn](const char* msg) {
slog.e << "DebugServer: " << msg << ": " << request->query_string << io::endl;
mg_printf(conn, kErrorHeader.data(), "application/txt");
mg_write(conn, msg, strlen(msg));
return true;
};
if (uri == "/api/active") {
mServer->updateActiveVariants();
mg_printf(conn, kSuccessHeader.data(), "application/json");
mg_printf(conn, "{");
// If the backend has not been resolved to Vulkan, Metal, etc., then return an empty
// list. This can occur if the server is matinfo rather than an actual Filament session.
if (mServer->mBackend == backend::Backend::DEFAULT) {
mg_printf(conn, "}");
return true;
}
int index = 0;
for (const auto& pair : mServer->mMaterialRecords) {
const auto& record = pair.second;
ChunkContainer package(record.package, record.packageSize);
if (!package.parse()) {
return error(__LINE__);
}
JsonWriter writer;
if (!writer.writeActiveInfo(package, mServer->mBackend, record.activeVariants)) {
return error(__LINE__);
}
const bool last = (++index) == mServer->mMaterialRecords.size();
mg_printf(conn, "\"%8.8x\": %s %s", pair.first, writer.getJsonString(),
last ? "" : ",");
}
mg_printf(conn, "}");
return true;
}
if (uri == "/api/matids") {
mg_printf(conn, kSuccessHeader.data(), "application/json");
mg_printf(conn, "[");
int index = 0;
for (const auto& record : mServer->mMaterialRecords) {
const bool last = (++index) == mServer->mMaterialRecords.size();
mg_printf(conn, "\"%8.8x\" %s", record.first, last ? "" : ",");
}
mg_printf(conn, "]");
return true;
}
if (uri == "/api/materials") {
mg_printf(conn, kSuccessHeader.data(), "application/json");
mg_printf(conn, "[");
int index = 0;
for (const auto& record : mServer->mMaterialRecords) {
const bool last = (++index) == mServer->mMaterialRecords.size();
ChunkContainer package(record.second.package, record.second.packageSize);
if (!package.parse()) {
return error(__LINE__);
}
JsonWriter writer;
if (!writer.writeMaterialInfo(package)) {
return error(__LINE__);
}
mg_printf(conn, "{ \"matid\": \"%8.8x\", %s } %s", record.first,
writer.getJsonString(), last ? "" : ",");
}
mg_printf(conn, "]");
return true;
}
if (!request->query_string) {
return error(__LINE__);
}
const size_t qlength = strlen(request->query_string);
char matid[9] = {};
if (mg_get_var(request->query_string, qlength, "matid", matid, sizeof(matid)) < 0) {
return error(__LINE__);
}
const uint32_t id = strtoul(matid, nullptr, 16);
const DebugServer::MaterialRecord* result = mServer->getRecord(id);
if (result == nullptr) {
return error(__LINE__);
}
ChunkContainer package(result->package, result->packageSize);
if (!package.parse()) {
return error(__LINE__);
}
if (uri == "/api/material") {
JsonWriter writer;
if (!writer.writeMaterialInfo(package)) {
return error(__LINE__);
}
mg_printf(conn, kSuccessHeader.data(), "application/json");
mg_printf(conn, "{ %s }", writer.getJsonString());
return true;
}
const std::string_view glsl("glsl");
const std::string_view msl("msl");
const std::string_view spirv("spirv");
char type[6] = {};
if (mg_get_var(request->query_string, qlength, "type", type, sizeof(type)) < 0) {
return error(__LINE__);
}
std::string_view const language(type, strlen(type));
char glindex[4] = {};
char vkindex[4] = {};
char metalindex[4] = {};
mg_get_var(request->query_string, qlength, "glindex", glindex, sizeof(glindex));
mg_get_var(request->query_string, qlength, "vkindex", vkindex, sizeof(vkindex));
mg_get_var(request->query_string, qlength, "metalindex", metalindex, sizeof(metalindex));
if (!glindex[0] && !vkindex[0] && !metalindex[0]) {
return error(__LINE__);
}
if (uri != "/api/shader") {
return error(__LINE__);
}
if (glindex[0]) {
if (language != glsl) {
return softError("Only GLSL is supported.");
}
FixedCapacityVector<ShaderInfo> info(getShaderCount(package, ChunkType::MaterialGlsl));
if (!getShaderInfo(package, info.data(), ChunkType::MaterialGlsl)) {
return error(__LINE__);
}
const int shaderIndex = std::stoi(glindex);
if (shaderIndex >= info.size()) {
return error(__LINE__);
}
ShaderExtractor extractor(ShaderLanguage::ESSL3, result->package, result->packageSize);
if (!extractor.parse()) {
return error(__LINE__);
}
const auto& item = info[shaderIndex];
filaflat::ShaderContent content;
extractor.getShader(item.shaderModel, item.variant, item.pipelineStage, content);
mg_printf(conn, kSuccessHeader.data(), "application/txt");
mg_write(conn, content.data(), content.size() - 1);
return true;
}
if (vkindex[0]) {
ShaderExtractor extractor(ShaderLanguage::SPIRV, result->package, result->packageSize);
if (!extractor.parse()) {
return error(__LINE__);
}
filaflat::ShaderContent content;
FixedCapacityVector<ShaderInfo> info(getShaderCount(package, ChunkType::MaterialSpirv));
if (!getShaderInfo(package, info.data(), ChunkType::MaterialSpirv)) {
return error(__LINE__);
}
const int shaderIndex = std::stoi(vkindex);
if (shaderIndex >= info.size()) {
return error(__LINE__);
}
const auto& item = info[shaderIndex];
extractor.getShader(item.shaderModel, item.variant, item.pipelineStage, content);
if (language == spirv) {
spirvToAsm(conn, (const uint32_t*) content.data(), content.size());
return true;
}
if (language == glsl) {
spirvToGlsl(item.shaderModel, conn, (const uint32_t*) content.data(), content.size());
return true;
}
return softError("Only SPIRV is supported.");
}
if (metalindex[0]) {
ShaderExtractor extractor(ShaderLanguage::MSL, result->package, result->packageSize);
if (!extractor.parse()) {
return error(__LINE__);
}
filaflat::ShaderContent content;
FixedCapacityVector<ShaderInfo> info(getShaderCount(package, ChunkType::MaterialMetal));
if (!getShaderInfo(package, info.data(), ChunkType::MaterialMetal)) {
return error(__LINE__);
}
const int shaderIndex = std::stoi(metalindex);
if (shaderIndex >= info.size()) {
return error(__LINE__);
}
const auto& item = info[shaderIndex];
extractor.getShader(item.shaderModel, item.variant, item.pipelineStage, content);
if (language == msl) {
mg_printf(conn, kSuccessHeader.data(), "application/txt");
mg_write(conn, content.data(), content.size() - 1);
return true;
}
return softError("Only MSL is supported.");
}
return error(__LINE__);
}
private:
DebugServer* mServer;
};
class WebSocketHandler : public CivetWebSocketHandler {
public:
WebSocketHandler(DebugServer* server) : mServer(server) {}
@@ -386,81 +130,6 @@ public:
bool handleData(CivetServer *server, struct mg_connection *conn, int bits, char *data,
size_t size) override {
// First check if this chunk is a continuation of a partial existing message.
if (mServer->mChunkedMessageRemaining > 0) {
const CString chunk(data, size);
const size_t pos = mServer->mChunkedMessage.size();
// Append the partial existing message.
mServer->mChunkedMessage = mServer->mChunkedMessage.insert(pos, chunk);
// Determine number of outstanding bytes.
if (size > mServer->mChunkedMessageRemaining) {
mServer->mChunkedMessageRemaining = 0;
} else {
mServer->mChunkedMessageRemaining -= size;
}
// Return early and wait for more chunks if some bytes are still outstanding.
if (mServer->mChunkedMessageRemaining > 0) {
return true;
}
data = mServer->mChunkedMessage.data();
size = mServer->mChunkedMessage.size();
// Ignore the handshake message that occurs after startup.
} else if (size < 8) {
return true;
}
mServer->mChunkedMessageRemaining = 0;
// Every WebSocket message is prefixed with a command name followed by a space.
//
// For now we simply use istringstream for parsing, so command arguments are delimited
// with space characters.
//
// The "API index" matches the values of filament::backend::Backend (zero is invalid).
//
// The "shader index" is a zero-based index into the list of variants using the order that
// they appear in the package, where each API (GL / VK / Metal) has its own list.
//
// Commands:
//
// EDIT [material id] [api index] [shader index] [shader length] [shader source....]
//
const static std::string_view kEditCmd = "EDIT ";
const static size_t kEditCmdLength = kEditCmd.size();
if (0 == strncmp(data, kEditCmd.data(), kEditCmdLength)) {
std::string command(data + kEditCmdLength, size - kEditCmdLength);
std::istringstream str(command);
uint32_t matid;
int api;
int shaderIndex;
int shaderLength;
str >> std::hex >> matid >> std::dec >> api >> shaderIndex >> shaderLength;
const char* source = data + kEditCmdLength + str.tellg() + 1;
const size_t remaining = size - kEditCmdLength - str.tellg();
// Return early and wait for more chunks if some bytes are still outstanding.
if (remaining < shaderLength + 1) {
mServer->mChunkedMessage = CString(data, size);
mServer->mChunkedMessageRemaining = shaderLength + 1 - remaining;
return true;
}
mServer->handleEditCommand(matid, backend::Backend(api), shaderIndex, source,
shaderLength);
return true;
}
const std::string firstFewChars(data, std::min(size, size_t(8)));
slog.e << "Bad WebSocket message. First few characters: "
<< "[" << firstFewChars << "]" << io::endl;
return false;
}
@@ -470,7 +139,7 @@ public:
}
// Notify all JavaScript clients that a new material package has been loaded.
void addMaterial(const DebugServer::MaterialRecord& material) {
void addMaterial(MaterialRecord const& material) {
for (auto connection : mConnections) {
char matid[9] = {};
snprintf(matid, sizeof(matid), "%8.8x", material.key);
@@ -512,10 +181,10 @@ DebugServer::DebugServer(Backend backend, int port) : mBackend(backend) {
}
mFileHandler = new FileRequestHandler(this);
mRestHandler = new RestRequestHandler(this);
mApiHandler = new ApiHandler(this);
mWebSocketHandler = new WebSocketHandler(this);
mServer->addHandler("/api", mRestHandler);
mServer->addHandler("/api", mApiHandler);
mServer->addHandler("", mFileHandler);
mServer->addWebSocketHandler("", mWebSocketHandler);
@@ -529,7 +198,7 @@ DebugServer::~DebugServer() {
delete [] pair.second.package;
}
delete mFileHandler;
delete mRestHandler;
delete mApiHandler;
delete mServer;
}
@@ -559,7 +228,7 @@ void DebugServer::removeMaterial(MaterialKey key) {
mMaterialRecords.erase(key);
}
const DebugServer::MaterialRecord* DebugServer::getRecord(const MaterialKey& key) const {
const MaterialRecord* DebugServer::getRecord(const MaterialKey& key) const {
const auto& iter = mMaterialRecords.find(key);
return iter == mMaterialRecords.end() ? nullptr : &iter->second;
}
@@ -649,5 +318,4 @@ bool DebugServer::handleEditCommand(const MaterialKey& key, backend::Backend api
return true;
}
} // namespace matdbg
} // namespace filament
} // namespace filament::matdbg