The JSON response to /api/active became malformed after #4465 because raw hex strings need to be enclosed by quotes. This commit changes the variant format in the /api/materials response to be consistent with one used for /api/active. By using integers instead of strings, we're avoiding the need to parse integers at run time. The JSON error did not appear in the Chrome console because it was being silenced as a hack to appease "matinfo --web-server". I fix this by removing the hack and simply emitting a valid response when there's no live backend. Also fixed the display of materials, which were always being marked as active even when they had no active variants.
659 lines
23 KiB
C++
659 lines
23 KiB
C++
/*
|
|
* Copyright (C) 2019 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 <matdbg/DebugServer.h>
|
|
|
|
#include <CivetServer.h>
|
|
|
|
#include <utils/FixedCapacityVector.h>
|
|
#include <utils/Hash.h>
|
|
#include <utils/Log.h>
|
|
|
|
#include <spirv_glsl.hpp>
|
|
#include <spirv-tools/libspirv.h>
|
|
|
|
#include <matdbg/JsonWriter.h>
|
|
#include <matdbg/ShaderExtractor.h>
|
|
#include <matdbg/ShaderInfo.h>
|
|
#include <matdbg/ShaderReplacer.h>
|
|
|
|
#include <filaflat/ChunkContainer.h>
|
|
|
|
#include <tsl/robin_set.h>
|
|
|
|
#include <backend/DriverEnums.h>
|
|
|
|
#include "sca/GLSLTools.h"
|
|
|
|
#include <sstream>
|
|
#include <string>
|
|
|
|
using utils::FixedCapacityVector;
|
|
|
|
// If set to 0, this serves HTML from a resgen resource. Use 1 only during local development, which
|
|
// serves files directly from the source code tree.
|
|
#define SERVE_FROM_SOURCE_TREE 0
|
|
|
|
#if !SERVE_FROM_SOURCE_TREE
|
|
#include "matdbg_resources.h"
|
|
#endif
|
|
|
|
namespace filament {
|
|
namespace matdbg {
|
|
|
|
using namespace utils;
|
|
using namespace filament::backend;
|
|
|
|
using filaflat::ChunkContainer;
|
|
using filamat::ChunkType;
|
|
|
|
static const StaticString kSuccessHeader =
|
|
"HTTP/1.1 200 OK\r\nContent-Type: %s\r\n"
|
|
"Connection: close\r\n\r\n";
|
|
|
|
static const StaticString 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 context = spvContextCreate(SPV_ENV_UNIVERSAL_1_0);
|
|
spv_text text = nullptr;
|
|
const uint32_t options = SPV_BINARY_TO_TEXT_OPTION_INDENT |
|
|
SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES;
|
|
spvBinaryToText(context, spirv, size / 4, options, &text, nullptr);
|
|
|
|
mg_printf(conn, kSuccessHeader.c_str(), "application/txt");
|
|
mg_write(conn, text->str, text->length);
|
|
spvTextDestroy(text);
|
|
spvContextDestroy(context);
|
|
}
|
|
|
|
static void spirvToGlsl(struct mg_connection *conn, const uint32_t* spirv, size_t size) {
|
|
auto glsl = ShaderExtractor::spirvToGLSL(spirv, size / 4);
|
|
mg_printf(conn, kSuccessHeader.c_str(), "application/txt");
|
|
mg_printf(conn, glsl.c_str(), glsl.size());
|
|
}
|
|
|
|
class FileRequestHandler : public CivetHandler {
|
|
public:
|
|
FileRequestHandler(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->request_uri);
|
|
if (uri == "/" || uri == "/index.html") {
|
|
#if SERVE_FROM_SOURCE_TREE
|
|
mg_send_file(conn, "libs/matdbg/web/index.html");
|
|
#else
|
|
mg_printf(conn, kSuccessHeader.c_str(), "text/html");
|
|
mg_write(conn, mServer->mHtml.c_str(), mServer->mHtml.size());
|
|
#endif
|
|
return true;
|
|
}
|
|
if (uri == "/style.css") {
|
|
#if SERVE_FROM_SOURCE_TREE
|
|
mg_send_file(conn, "libs/matdbg/web/style.css");
|
|
#else
|
|
mg_printf(conn, kSuccessHeader.c_str(), "text/css");
|
|
mg_write(conn, mServer->mCss.c_str(), mServer->mCss.size());
|
|
#endif
|
|
return true;
|
|
}
|
|
if (uri == "/script.js") {
|
|
#if SERVE_FROM_SOURCE_TREE
|
|
mg_send_file(conn, "libs/matdbg/web/script.js");
|
|
#else
|
|
mg_printf(conn, kSuccessHeader.c_str(), "text/javascript");
|
|
mg_write(conn, mServer->mJavascript.c_str(), mServer->mJavascript.size());
|
|
#endif
|
|
return true;
|
|
}
|
|
slog.e << "DebugServer: bad request at line " << __LINE__ << ": " << uri << io::endl;
|
|
return false;
|
|
}
|
|
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.c_str(), "application/txt");
|
|
mg_write(conn, msg, strlen(msg));
|
|
return true;
|
|
};
|
|
|
|
if (uri == "/api/active") {
|
|
mServer->updateActiveVariants();
|
|
mg_printf(conn, kSuccessHeader.c_str(), "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.c_str(), "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.c_str(), "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 = strtol(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.c_str(), "application/json");
|
|
mg_printf(conn, "{ %s }", writer.getJsonString());
|
|
return true;
|
|
}
|
|
|
|
const StaticString glsl("glsl");
|
|
const StaticString msl("msl");
|
|
const StaticString spirv("spirv");
|
|
|
|
char type[6] = {};
|
|
if (mg_get_var(request->query_string, qlength, "type", type, sizeof(type)) < 0) {
|
|
return error(__LINE__);
|
|
}
|
|
|
|
CString 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 (!getGlShaderInfo(package, info.data())) {
|
|
return error(__LINE__);
|
|
}
|
|
|
|
const int shaderIndex = std::stoi(glindex);
|
|
if (shaderIndex >= info.size()) {
|
|
return error(__LINE__);
|
|
}
|
|
|
|
ShaderExtractor extractor(Backend::OPENGL, result->package, result->packageSize);
|
|
if (!extractor.parse()) {
|
|
return error(__LINE__);
|
|
}
|
|
|
|
const auto& item = info[shaderIndex];
|
|
filaflat::ShaderBuilder builder;
|
|
extractor.getShader(item.shaderModel, item.variant, item.pipelineStage, builder);
|
|
|
|
mg_printf(conn, kSuccessHeader.c_str(), "application/txt");
|
|
mg_write(conn, builder.data(), builder.size() - 1);
|
|
return true;
|
|
}
|
|
|
|
if (vkindex[0]) {
|
|
ShaderExtractor extractor(Backend::VULKAN, result->package, result->packageSize);
|
|
if (!extractor.parse()) {
|
|
return error(__LINE__);
|
|
}
|
|
|
|
filaflat::ShaderBuilder builder;
|
|
FixedCapacityVector<ShaderInfo> info(getShaderCount(package, ChunkType::MaterialSpirv));
|
|
if (!getVkShaderInfo(package, info.data())) {
|
|
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, builder);
|
|
|
|
if (language == spirv) {
|
|
spirvToAsm(conn, (const uint32_t*) builder.data(), builder.size());
|
|
return true;
|
|
}
|
|
|
|
if (language == glsl) {
|
|
spirvToGlsl(conn, (const uint32_t*) builder.data(), builder.size());
|
|
return true;
|
|
}
|
|
|
|
return softError("Only SPIRV is supported.");
|
|
}
|
|
|
|
if (metalindex[0]) {
|
|
ShaderExtractor extractor(Backend::METAL, result->package, result->packageSize);
|
|
if (!extractor.parse()) {
|
|
return error(__LINE__);
|
|
}
|
|
|
|
filaflat::ShaderBuilder builder;
|
|
FixedCapacityVector<ShaderInfo> info(getShaderCount(package, ChunkType::MaterialMetal));
|
|
if (!getMetalShaderInfo(package, info.data())) {
|
|
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, builder);
|
|
|
|
if (language == msl) {
|
|
mg_printf(conn, kSuccessHeader.c_str(), "application/txt");
|
|
mg_write(conn, builder.data(), builder.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) {}
|
|
|
|
bool handleConnection(CivetServer *server, const struct mg_connection *conn) override {
|
|
return true;
|
|
}
|
|
|
|
void handleReadyState(CivetServer *server, struct mg_connection *conn) override {
|
|
mConnections.insert(conn);
|
|
}
|
|
|
|
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 StaticString kEditCmd = "EDIT ";
|
|
const static size_t kEditCmdLength = kEditCmd.size();
|
|
|
|
if (0 == strncmp(data, kEditCmd.c_str(), kEditCmdLength)) {
|
|
std::istringstream str(data + kEditCmdLength);
|
|
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;
|
|
}
|
|
|
|
void handleClose(CivetServer *server, const struct mg_connection *conn) override {
|
|
struct mg_connection *key = const_cast<struct mg_connection *>(conn);
|
|
mConnections.erase(key);
|
|
}
|
|
|
|
// Notify all JavaScript clients that a new material package has been loaded.
|
|
void addMaterial(const DebugServer::MaterialRecord& material) {
|
|
for (auto connection : mConnections) {
|
|
char matid[9] = {};
|
|
snprintf(matid, sizeof(matid), "%8.8x", material.key);
|
|
mg_websocket_write(connection, MG_WEBSOCKET_OPCODE_TEXT, matid, 8);
|
|
}
|
|
}
|
|
|
|
private:
|
|
DebugServer* mServer;
|
|
tsl::robin_set<struct mg_connection*> mConnections;
|
|
};
|
|
|
|
DebugServer::DebugServer(Backend backend, int port) : mBackend(backend) {
|
|
#if !SERVE_FROM_SOURCE_TREE
|
|
mHtml = CString((const char*) MATDBG_RESOURCES_INDEX_DATA, MATDBG_RESOURCES_INDEX_SIZE - 1);
|
|
mJavascript = CString((const char*) MATDBG_RESOURCES_SCRIPT_DATA, MATDBG_RESOURCES_SCRIPT_SIZE - 1);
|
|
mCss = CString((const char*) MATDBG_RESOURCES_STYLE_DATA, MATDBG_RESOURCES_STYLE_SIZE - 1);
|
|
#endif
|
|
|
|
// By default the server spawns 50 threads so we override this to 10. According to the civetweb
|
|
// documentation, "it is recommended to use num_threads of at least 5, since browsers often
|
|
/// establish multiple connections to load a single web page, including all linked documents
|
|
// (CSS, JavaScript, images, ...)." If this count is too small, the web app basically hangs.
|
|
const char* kServerOptions[] = {
|
|
"listening_ports", "8080",
|
|
"num_threads", "10",
|
|
"error_log_file", "civetweb.txt",
|
|
nullptr
|
|
};
|
|
std::string portString = std::to_string(port);
|
|
kServerOptions[1] = portString.c_str();
|
|
|
|
mServer = new CivetServer(kServerOptions);
|
|
if (!mServer->getContext()) {
|
|
delete mServer;
|
|
mServer = nullptr;
|
|
slog.e << "Unable to start DebugServer, see civetweb.txt for details." << io::endl;
|
|
return;
|
|
}
|
|
|
|
mFileHandler = new FileRequestHandler(this);
|
|
mRestHandler = new RestRequestHandler(this);
|
|
mWebSocketHandler = new WebSocketHandler(this);
|
|
|
|
mServer->addHandler("/api", mRestHandler);
|
|
mServer->addHandler("", mFileHandler);
|
|
mServer->addWebSocketHandler("", mWebSocketHandler);
|
|
|
|
slog.i << "DebugServer listening at http://localhost:" << port << io::endl;
|
|
filamat::GLSLTools::init();
|
|
}
|
|
|
|
DebugServer::~DebugServer() {
|
|
filamat::GLSLTools::shutdown();
|
|
for (auto& pair : mMaterialRecords) {
|
|
delete [] pair.second.package;
|
|
}
|
|
delete mFileHandler;
|
|
delete mRestHandler;
|
|
delete mServer;
|
|
}
|
|
|
|
MaterialKey
|
|
DebugServer::addMaterial(const CString& name, const void* data, size_t size, void* userdata) {
|
|
filaflat::ChunkContainer* container = new filaflat::ChunkContainer(data, size);
|
|
if (!container->parse()) {
|
|
slog.e << "DebugServer: unable to parse material package: " << name.c_str() << io::endl;
|
|
return {};
|
|
}
|
|
|
|
const uint32_t seed = 42;
|
|
auto words = (const uint32_t*) data;
|
|
MaterialKey key = utils::hash::murmur3(words, size / 4, seed);
|
|
|
|
// Retain a copy of the package to permit queries after the client application has
|
|
// freed up the original material package.
|
|
uint8_t* package = new uint8_t[size];
|
|
memcpy(package, data, size);
|
|
|
|
MaterialRecord info = {userdata, package, size, name, key};
|
|
mMaterialRecords.insert({key, info});
|
|
mWebSocketHandler->addMaterial(info);
|
|
return key;
|
|
}
|
|
|
|
void DebugServer::removeMaterial(MaterialKey key) {
|
|
mMaterialRecords.erase(key);
|
|
}
|
|
|
|
const DebugServer::MaterialRecord* DebugServer::getRecord(const MaterialKey& key) const {
|
|
const auto& iter = mMaterialRecords.find(key);
|
|
return iter == mMaterialRecords.end() ? nullptr : &iter->second;
|
|
}
|
|
|
|
void DebugServer::updateActiveVariants() {
|
|
if (mQueryCallback) {
|
|
auto curr = mMaterialRecords.begin();
|
|
auto end = mMaterialRecords.end();
|
|
while (curr != end) {
|
|
auto& value = curr.value();
|
|
VariantList& result = value.activeVariants;
|
|
mQueryCallback(value.userdata, &result);
|
|
++curr;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool DebugServer::handleEditCommand(const MaterialKey& key, backend::Backend api, int shaderIndex,
|
|
const char* source, size_t size) {
|
|
const auto error = [](int line) {
|
|
slog.e << "DebugServer: Unable to apply shader edit at line " << line << io::endl;
|
|
return false;
|
|
};
|
|
|
|
if (mMaterialRecords.find(key) == mMaterialRecords.end()) {
|
|
return error(__LINE__);
|
|
}
|
|
MaterialRecord& material = mMaterialRecords[key];
|
|
filaflat::ChunkContainer package(material.package, material.packageSize);
|
|
if (!package.parse()) {
|
|
return error(__LINE__);
|
|
}
|
|
|
|
FixedCapacityVector<ShaderInfo> infos;
|
|
size_t shaderCount;
|
|
switch (api) {
|
|
case backend::Backend::OPENGL: {
|
|
shaderCount = getShaderCount(package, ChunkType::MaterialGlsl);
|
|
infos.reserve(shaderCount);
|
|
infos.resize(shaderCount);
|
|
if (!getGlShaderInfo(package, infos.data())) {
|
|
return error(__LINE__);
|
|
}
|
|
break;
|
|
}
|
|
case backend::Backend::VULKAN: {
|
|
shaderCount = getShaderCount(package, ChunkType::MaterialSpirv);
|
|
infos.reserve(shaderCount);
|
|
infos.resize(shaderCount);
|
|
if (!getVkShaderInfo(package, infos.data())) {
|
|
return error(__LINE__);
|
|
}
|
|
break;
|
|
}
|
|
case backend::Backend::METAL: {
|
|
shaderCount = getShaderCount(package, ChunkType::MaterialMetal);
|
|
infos.reserve(shaderCount);
|
|
infos.resize(shaderCount);
|
|
if (!getMetalShaderInfo(package, infos.data())) {
|
|
return error(__LINE__);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
error(__LINE__);
|
|
}
|
|
|
|
if (shaderIndex < 0 || shaderIndex >= shaderCount) {
|
|
return error(__LINE__);
|
|
}
|
|
|
|
const ShaderInfo info = infos[shaderIndex];
|
|
ShaderReplacer editor(api, package.getData(), package.getSize());
|
|
if (!editor.replaceShaderSource(info.shaderModel, info.variant, info.pipelineStage, source, size)) {
|
|
return error(__LINE__);
|
|
}
|
|
|
|
delete [] material.package;
|
|
|
|
material.package = editor.getEditedPackage();
|
|
material.packageSize = editor.getEditedSize();
|
|
|
|
if (mEditCallback) {
|
|
mEditCallback(material.userdata, material.name, material.package, material.packageSize);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace matdbg
|
|
} // namespace filament
|