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:
@@ -22,6 +22,8 @@ set(PUBLIC_HDRS
|
||||
)
|
||||
|
||||
set(SRCS
|
||||
src/ApiHandler.cpp
|
||||
src/ApiHandler.h
|
||||
src/CommonWriter.h
|
||||
src/CommonWriter.cpp
|
||||
src/DebugServer.cpp
|
||||
@@ -59,7 +61,6 @@ endif()
|
||||
set(DUMMY_SRC "${RESOURCE_DIR}/dummy.c")
|
||||
add_custom_command(OUTPUT ${DUMMY_SRC} COMMAND echo "//" > ${DUMMY_SRC})
|
||||
|
||||
|
||||
add_library(matdbg_resources ${DUMMY_SRC} ${RESGEN_SOURCE})
|
||||
set_target_properties(matdbg_resources PROPERTIES FOLDER Libs)
|
||||
|
||||
|
||||
@@ -27,11 +27,19 @@
|
||||
|
||||
class CivetServer;
|
||||
|
||||
namespace filament {
|
||||
namespace matdbg {
|
||||
namespace filament::matdbg {
|
||||
|
||||
using MaterialKey = uint32_t;
|
||||
|
||||
struct MaterialRecord {
|
||||
void* userdata;
|
||||
const uint8_t* package;
|
||||
size_t packageSize;
|
||||
utils::CString name;
|
||||
MaterialKey key;
|
||||
VariantList activeVariants;
|
||||
};
|
||||
|
||||
/**
|
||||
* Server-side material debugger.
|
||||
*
|
||||
@@ -41,6 +49,9 @@ using MaterialKey = uint32_t;
|
||||
*/
|
||||
class DebugServer {
|
||||
public:
|
||||
static std::string_view const kSuccessHeader;
|
||||
static std::string_view const kErrorHeader;
|
||||
|
||||
DebugServer(backend::Backend backend, int port);
|
||||
~DebugServer();
|
||||
|
||||
@@ -74,16 +85,7 @@ public:
|
||||
bool isReady() const { return mServer; }
|
||||
|
||||
private:
|
||||
struct MaterialRecord {
|
||||
void* userdata;
|
||||
const uint8_t* package;
|
||||
size_t packageSize;
|
||||
utils::CString name;
|
||||
MaterialKey key;
|
||||
VariantList activeVariants;
|
||||
};
|
||||
|
||||
const MaterialRecord* getRecord(const MaterialKey& key) const;
|
||||
MaterialRecord const* getRecord(const MaterialKey& key) const;
|
||||
|
||||
void updateActiveVariants();
|
||||
|
||||
@@ -109,15 +111,14 @@ private:
|
||||
QueryCallback mQueryCallback = nullptr;
|
||||
|
||||
class FileRequestHandler* mFileHandler = nullptr;
|
||||
class RestRequestHandler* mRestHandler = nullptr;
|
||||
class ApiHandler* mApiHandler = nullptr;
|
||||
class WebSocketHandler* mWebSocketHandler = nullptr;
|
||||
|
||||
friend class FileRequestHandler;
|
||||
friend class RestRequestHandler;
|
||||
friend class ApiHandler;
|
||||
friend class WebSocketHandler;
|
||||
};
|
||||
|
||||
} // namespace matdbg
|
||||
} // namespace filament
|
||||
} // namespace filament::matdbg
|
||||
|
||||
#endif // MATDBG_DEBUGSERVER_H
|
||||
|
||||
359
libs/matdbg/src/ApiHandler.cpp
Normal file
359
libs/matdbg/src/ApiHandler.cpp
Normal file
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
#include "ApiHandler.h"
|
||||
|
||||
#include <backend/DriverEnums.h>
|
||||
|
||||
#include <matdbg/DebugServer.h>
|
||||
#include <matdbg/JsonWriter.h>
|
||||
#include <matdbg/ShaderExtractor.h>
|
||||
#include <matdbg/ShaderInfo.h>
|
||||
|
||||
#include <filaflat/ChunkContainer.h>
|
||||
#include <utils/FixedCapacityVector.h>
|
||||
#include <utils/Log.h>
|
||||
|
||||
#include <CivetServer.h>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace filament::matdbg {
|
||||
|
||||
using namespace filament::backend;
|
||||
|
||||
namespace {
|
||||
|
||||
auto const& kSuccessHeader = DebugServer::kSuccessHeader;
|
||||
auto const& kErrorHeader = DebugServer::kErrorHeader;
|
||||
|
||||
void spirvToAsm(struct mg_connection *conn, uint32_t const* 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());
|
||||
}
|
||||
|
||||
void spirvToGlsl(ShaderModel shaderModel, struct mg_connection *conn,
|
||||
uint32_t const* 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());
|
||||
}
|
||||
|
||||
} // anonymous
|
||||
|
||||
using filaflat::ChunkContainer;
|
||||
using filamat::ChunkType;
|
||||
using utils::FixedCapacityVector;
|
||||
|
||||
static auto const error = [](int line, std::string const& uri) {
|
||||
utils::slog.e << "DebugServer: 404 at line " << line << ": " << uri << utils::io::endl;
|
||||
return false;
|
||||
};
|
||||
|
||||
MaterialRecord const* ApiHandler::getMaterialRecord(struct mg_request_info const* request) {
|
||||
size_t const qlength = strlen(request->query_string);
|
||||
char matid[9] = {};
|
||||
if (mg_get_var(request->query_string, qlength, "matid", matid, sizeof(matid)) < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
uint32_t const id = strtoul(matid, nullptr, 16);
|
||||
return mServer->getRecord(id);
|
||||
}
|
||||
|
||||
bool ApiHandler::handleGetApiShader(struct mg_connection* conn,
|
||||
struct mg_request_info const* request) {
|
||||
auto const softError = [conn, request](char const* msg) {
|
||||
utils::slog.e << "DebugServer: " << msg << ": " << request->query_string << utils::io::endl;
|
||||
mg_printf(conn, kErrorHeader.data(), "application/txt");
|
||||
mg_write(conn, msg, strlen(msg));
|
||||
return true;
|
||||
};
|
||||
|
||||
MaterialRecord const* result = getMaterialRecord(request);
|
||||
std::string const& uri = request->local_uri;
|
||||
|
||||
if (!result) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
ChunkContainer package(result->package, result->packageSize);
|
||||
if (!package.parse()) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
std::string_view const glsl("glsl");
|
||||
std::string_view const msl("msl");
|
||||
std::string_view const spirv("spirv");
|
||||
size_t const qlength = strlen(request->query_string);
|
||||
|
||||
char type[6] = {};
|
||||
if (mg_get_var(request->query_string, qlength, "type", type, sizeof(type)) < 0) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
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__, uri);
|
||||
}
|
||||
|
||||
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__, uri);
|
||||
}
|
||||
|
||||
int const shaderIndex = std::stoi(glindex);
|
||||
if (shaderIndex >= info.size()) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
ShaderExtractor extractor(ShaderLanguage::ESSL3, result->package, result->packageSize);
|
||||
if (!extractor.parse()) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
auto const& 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__, uri);
|
||||
}
|
||||
|
||||
FixedCapacityVector<ShaderInfo> info(getShaderCount(package, ChunkType::MaterialSpirv));
|
||||
if (!getShaderInfo(package, info.data(), ChunkType::MaterialSpirv)) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
int const shaderIndex = std::stoi(vkindex);
|
||||
if (shaderIndex >= info.size()) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
auto const& item = info[shaderIndex];
|
||||
filaflat::ShaderContent content;
|
||||
extractor.getShader(item.shaderModel, item.variant, item.pipelineStage, content);
|
||||
|
||||
if (language == spirv) {
|
||||
spirvToAsm(conn, (uint32_t const*) content.data(), content.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (language == glsl) {
|
||||
spirvToGlsl(item.shaderModel, conn, (uint32_t const*) 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__, uri);
|
||||
}
|
||||
|
||||
FixedCapacityVector<ShaderInfo> info(getShaderCount(package, ChunkType::MaterialMetal));
|
||||
if (!getShaderInfo(package, info.data(), ChunkType::MaterialMetal)) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
int const shaderIndex = std::stoi(metalindex);
|
||||
if (shaderIndex >= info.size()) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
auto const& item = info[shaderIndex];
|
||||
filaflat::ShaderContent content;
|
||||
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__, uri);
|
||||
}
|
||||
|
||||
bool ApiHandler::handlePost(CivetServer* server, struct mg_connection* conn) {
|
||||
struct mg_request_info const* request = mg_get_request_info(conn);
|
||||
std::string const& uri = request->local_uri;
|
||||
|
||||
// For now we simply use istringstream for parsing, so command arguments are delimited
|
||||
// with space characters.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// POST body format:
|
||||
// [material id] [api index] [shader index] [shader source....]
|
||||
if (uri == "/api/edit") {
|
||||
struct mg_request_info const* req_info = mg_get_request_info(conn);
|
||||
size_t msgLen = req_info->content_length;
|
||||
|
||||
char buf[1024];
|
||||
size_t readLen = 0;
|
||||
std::stringstream sstream;
|
||||
while (readLen < msgLen) {
|
||||
int res = mg_read(conn, buf, sizeof(buf));
|
||||
if (res < 0) {
|
||||
utils::slog.e << "civet error parsing /api/edit body: " << res << utils::io::endl;
|
||||
break;
|
||||
}
|
||||
if (res == 0) {
|
||||
break;
|
||||
}
|
||||
readLen += res;
|
||||
sstream.write(buf, res);
|
||||
}
|
||||
uint32_t matid;
|
||||
int api;
|
||||
int shaderIndex;
|
||||
sstream >> std::hex >> matid >> std::dec >> api >> shaderIndex;
|
||||
std::string shader = sstream.str().substr(sstream.tellg());
|
||||
|
||||
mServer->handleEditCommand(matid, backend::Backend(api), shaderIndex, shader.c_str(),
|
||||
shader.size());
|
||||
|
||||
mg_printf(conn, "HTTP/1.1 200 OK\r\nConnection: close");
|
||||
return true;
|
||||
}
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
bool ApiHandler::handleGet(CivetServer* server, struct mg_connection* conn) {
|
||||
struct mg_request_info const* request = mg_get_request_info(conn);
|
||||
std::string const& uri = request->local_uri;
|
||||
|
||||
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 (auto const& pair: mServer->mMaterialRecords) {
|
||||
auto const& record = pair.second;
|
||||
ChunkContainer package(record.package, record.packageSize);
|
||||
if (!package.parse()) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
JsonWriter writer;
|
||||
if (!writer.writeActiveInfo(package, mServer->mBackend, record.activeVariants)) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
bool const 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 (auto const& record: mServer->mMaterialRecords) {
|
||||
bool const 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 (auto const& record: mServer->mMaterialRecords) {
|
||||
bool const last = (++index) == mServer->mMaterialRecords.size();
|
||||
|
||||
ChunkContainer package(record.second.package, record.second.packageSize);
|
||||
if (!package.parse()) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
JsonWriter writer;
|
||||
if (!writer.writeMaterialInfo(package)) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
mg_printf(conn, "{ \"matid\": \"%8.8x\", %s } %s", record.first, writer.getJsonString(),
|
||||
last ? "" : ",");
|
||||
}
|
||||
mg_printf(conn, "]");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uri == "/api/material") {
|
||||
MaterialRecord const* result = getMaterialRecord(request);
|
||||
if (!result) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
ChunkContainer package(result->package, result->packageSize);
|
||||
if (!package.parse()) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
JsonWriter writer;
|
||||
if (!writer.writeMaterialInfo(package)) {
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
mg_printf(conn, kSuccessHeader.data(), "application/json");
|
||||
mg_printf(conn, "{ %s }", writer.getJsonString());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (uri == "/api/shader") {
|
||||
return handleGetApiShader(conn, request);
|
||||
}
|
||||
|
||||
return error(__LINE__, uri);
|
||||
}
|
||||
|
||||
} // filament::matdbg
|
||||
55
libs/matdbg/src/ApiHandler.h
Normal file
55
libs/matdbg/src/ApiHandler.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2023 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef MATDBG_APIHANDLER_H
|
||||
#define MATDBG_APIHANDLER_H
|
||||
|
||||
#include <CivetServer.h>
|
||||
|
||||
namespace filament::matdbg {
|
||||
|
||||
class DebugServer;
|
||||
struct MaterialRecord;
|
||||
|
||||
// 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
|
||||
// POST /api/edit
|
||||
//
|
||||
class ApiHandler : public CivetHandler {
|
||||
public:
|
||||
explicit ApiHandler(DebugServer* server)
|
||||
: mServer(server) {}
|
||||
|
||||
bool handleGet(CivetServer* server, struct mg_connection* conn);
|
||||
|
||||
bool handlePost(CivetServer* server, struct mg_connection* conn);
|
||||
|
||||
~ApiHandler() = default;
|
||||
|
||||
private:
|
||||
bool handleGetApiShader(struct mg_connection* conn, struct mg_request_info const* request);
|
||||
MaterialRecord const* getMaterialRecord(struct mg_request_info const* request);
|
||||
DebugServer* mServer;
|
||||
};
|
||||
|
||||
} // filament::matdbg
|
||||
|
||||
#endif // MATDBG_APIHANDLER_H
|
||||
@@ -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
|
||||
|
||||
@@ -79,8 +79,9 @@ function rebuildMaterial() {
|
||||
}
|
||||
|
||||
const editedText = shader[gCurrentLanguage];
|
||||
const byteCount = new Blob([editedText]).size;
|
||||
gSocket.send(`EDIT ${gCurrentShader.matid} ${api} ${index} ${byteCount} ${editedText}`);
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('POST', '/api/edit');
|
||||
req.send(`${gCurrentShader.matid} ${api} ${index} ${editedText}`);
|
||||
}
|
||||
|
||||
document.querySelector("body").addEventListener("click", (evt) => {
|
||||
|
||||
Reference in New Issue
Block a user