diff --git a/libs/fgviewer/CMakeLists.txt b/libs/fgviewer/CMakeLists.txt index 892232f433a8..559c975408dd 100644 --- a/libs/fgviewer/CMakeLists.txt +++ b/libs/fgviewer/CMakeLists.txt @@ -23,6 +23,7 @@ set(SRCS src/ApiHandler.h src/DebugServer.cpp src/FrameGraphInfo.cpp + src/JsonWriter.cpp ) # ================================================================================================== diff --git a/libs/fgviewer/include/fgviewer/FrameGraphInfo.h b/libs/fgviewer/include/fgviewer/FrameGraphInfo.h index 7de8dd02def8..d1e3b292df9a 100644 --- a/libs/fgviewer/include/fgviewer/FrameGraphInfo.h +++ b/libs/fgviewer/include/fgviewer/FrameGraphInfo.h @@ -1,5 +1,5 @@ /* -* Copyright (C) 2024 The Android Open Source Project + * Copyright (C) 2024 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. @@ -36,17 +36,25 @@ class FrameGraphInfo { FrameGraphInfo(FrameGraphInfo const &) = delete; + bool operator==(const FrameGraphInfo& rhs) const; + struct Pass { Pass(utils::CString name, std::vector reads, std::vector writes); + bool operator==(const Pass& rhs) const; + utils::CString name; std::vector reads; std::vector writes; }; struct Resource { + bool operator==(const Resource& rhs) const; + struct Property { + bool operator==(const Property& rhs) const; + utils::CString name; utils::CString value; }; @@ -70,6 +78,12 @@ class FrameGraphInfo { // The incoming passes should be sorted by the execution order. void setPasses(std::vector sortedPasses); + const char* getViewName() const; + + const std::vector& getPasses() const; + + const std::unordered_map& getResources() const; + private: utils::CString viewName; // The order of the passes in the vector indicates the execution diff --git a/libs/fgviewer/src/ApiHandler.cpp b/libs/fgviewer/src/ApiHandler.cpp index 729820d4047c..5f49c3605bd4 100644 --- a/libs/fgviewer/src/ApiHandler.cpp +++ b/libs/fgviewer/src/ApiHandler.cpp @@ -18,6 +18,7 @@ #include "ApiHandler.h" #include +#include #include #include @@ -40,29 +41,96 @@ auto const error = [](int line, std::string const& uri) { } // anonymous -bool ApiHandler::handleGetApiFgInfo(struct mg_connection* conn, - struct mg_request_info const* request) { - auto const softError = [conn, request](char const* msg) { - utils::slog.e << "[fgviewer] DebugServer: " << msg << ": " << request->query_string << utils::io::endl; - mg_printf(conn, kErrorHeader.data(), "application/txt"); - mg_write(conn, msg, strlen(msg)); +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.find("/api/status") == 0) { + return handleGetStatus(conn, request); + } + + if (uri == "/api/framegraphs") { + std::unique_lock const lock(mServer->mViewsMutex); + mg_printf(conn, kSuccessHeader.data(), "application/json"); + mg_printf(conn, "["); + int index = 0; + for (auto const& view: mServer->mViews) { + bool const last = (++index) == mServer->mViews.size(); + + JsonWriter writer; + if (!writer.writeFrameGraphInfo(view.second)) { + return error(__LINE__, uri); + } + + mg_printf(conn, "{ \"fgid\": \"%8.8x\", %s } %s", view.first, writer.getJsonString(), + last ? "" : ","); + } + mg_printf(conn, "]"); + return true; + } + + if (uri == "/api/framegraph") { + const FrameGraphInfo* result = getFrameGraphInfo(request); + if (!result) { + return error(__LINE__, uri); + } + + JsonWriter writer; + if (!writer.writeFrameGraphInfo(*result)) { + return error(__LINE__, uri); + } + mg_printf(conn, kSuccessHeader.data(), "application/json"); + mg_printf(conn, "{ %s }", writer.getJsonString()); return true; - }; + } - // TODO: Implement the method - return true; + return error(__LINE__, uri); } -void ApiHandler::addFrameGraph(FrameGraphInfo const* framegraph) { - // TODO: Implement the method +void ApiHandler::updateFrameGraph(ViewHandle view_handle) { + std::unique_lock const lock(mStatusMutex); + snprintf(statusFrameGraphId, sizeof(statusFrameGraphId), "%8.8x", view_handle); + mCurrentStatus++; + mStatusCondition.notify_all(); } +const FrameGraphInfo* ApiHandler::getFrameGraphInfo(struct mg_request_info const* request) { + size_t const qlength = strlen(request->query_string); + char fgid[9] = {}; + if (mg_get_var(request->query_string, qlength, "fgid", fgid, sizeof(fgid)) < 0) { + return nullptr; + } + uint32_t const id = strtoul(fgid, nullptr, 16); + std::unique_lock const lock(mServer->mViewsMutex); + const auto it = mServer->mViews.find(id); + return it == mServer->mViews.end() + ? nullptr + : &(it->second); +} -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; - - // TODO: Implement the method +bool ApiHandler::handleGetStatus(struct mg_connection* conn, + struct mg_request_info const* request) { + char const* qstr = request->query_string; + if (qstr && strcmp(qstr, "firstTime") == 0) { + mg_printf(conn, kSuccessHeader.data(), "application/txt"); + mg_write(conn, "0", 1); + return true; + } + + std::unique_lock lock(mStatusMutex); + uint64_t const currentStatusCount = mCurrentStatus; + if (mStatusCondition.wait_for(lock, 10s, + [this, currentStatusCount] { + return currentStatusCount < mCurrentStatus; + })) { + mg_printf(conn, kSuccessHeader.data(), "application/txt"); + mg_write(conn, statusFrameGraphId, 8); + } else { + mg_printf(conn, kSuccessHeader.data(), "application/txt"); + // Use '1' to indicate a no-op. This ensures that we don't block forever if the client is + // gone. + mg_write(conn, "1", 1); + } return true; } diff --git a/libs/fgviewer/src/ApiHandler.h b/libs/fgviewer/src/ApiHandler.h index b0ed9e785d39..b7e7cf7c7030 100644 --- a/libs/fgviewer/src/ApiHandler.h +++ b/libs/fgviewer/src/ApiHandler.h @@ -17,6 +17,8 @@ #ifndef FGVIEWER_APIHANDLER_H #define FGVIEWER_APIHANDLER_H +#include + #include namespace filament::fgviewer { @@ -27,7 +29,8 @@ struct FrameGraphInfo; // Handles the following REST requests, where {id} is an 8-digit hex string. // // GET /api/framegraphs -// GET /api/framegraph?fg={viewname} +// GET /api/framegraph?fg={fgid} +// GET /api/status // class ApiHandler : public CivetHandler { public: @@ -36,12 +39,25 @@ class ApiHandler : public CivetHandler { ~ApiHandler() = default; bool handleGet(CivetServer* server, struct mg_connection* conn); - void addFrameGraph(FrameGraphInfo const* frameGraph); + + void updateFrameGraph(ViewHandle view_handle); private: - bool handleGetApiFgInfo(struct mg_connection* conn, struct mg_request_info const* request); + const FrameGraphInfo* getFrameGraphInfo(struct mg_request_info const* request); + + bool handleGetStatus(struct mg_connection* conn, + struct mg_request_info const* request); DebugServer* mServer; + + std::mutex mStatusMutex; + std::condition_variable mStatusCondition; + char statusFrameGraphId[9] = {}; + + // This variable is to implement a *hanging* effect for /api/status. The call to /api/status + // will always block until statusMaterialId is updated again. The client is expected to keep + // calling /api/status (a constant "pull" to simulate a push). + uint64_t mCurrentStatus = 0; }; } // filament::fgviewer diff --git a/libs/fgviewer/src/DebugServer.cpp b/libs/fgviewer/src/DebugServer.cpp index 70d2af089478..268b410d22fa 100644 --- a/libs/fgviewer/src/DebugServer.cpp +++ b/libs/fgviewer/src/DebugServer.cpp @@ -110,6 +110,7 @@ ViewHandle DebugServer::createView(utils::CString name) { std::unique_lock lock(mViewsMutex); ViewHandle handle = mViewCounter++; mViews.emplace(handle, FrameGraphInfo(std::move(name))); + mApiHandler->updateFrameGraph(handle); return handle; } @@ -121,8 +122,19 @@ void DebugServer::destroyView(ViewHandle h) { void DebugServer::update(ViewHandle h, FrameGraphInfo info) { std::unique_lock lock(mViewsMutex); + const auto it = mViews.find(h); + if (it == mViews.end()) { + slog.w << "[fgviewer] Received update for unknown handle " << h; + return; + } + + bool has_changed = !(it->second == info); + if (!has_changed) + return; + mViews.erase(h); mViews.emplace(h, std::move(info)); + mApiHandler->updateFrameGraph(h); } } // namespace filament::fgviewer diff --git a/libs/fgviewer/src/FrameGraphInfo.cpp b/libs/fgviewer/src/FrameGraphInfo.cpp index 59fcaea489b5..90cca55bc169 100644 --- a/libs/fgviewer/src/FrameGraphInfo.cpp +++ b/libs/fgviewer/src/FrameGraphInfo.cpp @@ -1,5 +1,5 @@ /* -* Copyright (C) 2024 The Android Open Source Project + * Copyright (C) 2024 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. @@ -25,15 +25,33 @@ FrameGraphInfo::~FrameGraphInfo() = default; FrameGraphInfo::FrameGraphInfo(FrameGraphInfo&& rhs) noexcept = default; +bool FrameGraphInfo::operator==(const FrameGraphInfo& rhs) const { + return viewName == rhs.viewName + && passes == rhs.passes + && resources == rhs.resources; +} + FrameGraphInfo::Pass::Pass(utils::CString name, std::vector reads, std::vector writes): name(std::move(name)), reads(std::move(reads)), writes(std::move(writes)) {} +bool FrameGraphInfo::Pass::operator==(const Pass& rhs) const { + return name == rhs.name && reads == rhs.reads && writes == rhs.writes; +} + FrameGraphInfo::Resource::Resource(ResourceId id, utils::CString name, std::vector properties): id(id), name(std::move(name)), properties(std::move(properties)) {} +bool FrameGraphInfo::Resource::operator==(const Resource& rhs) const { + return id == rhs.id && name == rhs.name && properties == rhs.properties; +} + +bool FrameGraphInfo::Resource::Property::operator==(const Property &rhs) const { + return name == rhs.name && value == rhs.value; +} + void FrameGraphInfo::setResources( std::unordered_map resources) { this->resources = std::move(resources); @@ -43,4 +61,17 @@ void FrameGraphInfo::setPasses(std::vector sortedPasses) { passes = std::move(sortedPasses); } +const char* FrameGraphInfo::getViewName() const { + return viewName.c_str_safe(); +} + +const std::vector& FrameGraphInfo::getPasses() const { + return passes; +} + +const std::unordered_map& + FrameGraphInfo::getResources() const { + return resources; +} + } // namespace filament::fgviewer diff --git a/libs/fgviewer/src/JsonWriter.cpp b/libs/fgviewer/src/JsonWriter.cpp new file mode 100644 index 000000000000..ac025f4163cf --- /dev/null +++ b/libs/fgviewer/src/JsonWriter.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2025 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 +#include + +#include +#include + +namespace filament::fgviewer { + +namespace { +void writeJSONString(std::ostream& os, const char* str) { + os << '"'; + const char* p = str; + while (*p != '\0') { + switch (*p) { + case '"': os << "\\\""; break; + case '\\': os << "\\\\"; break; + case '\n': os << "\\n"; break; + case '\t': os << "\\t"; break; + default: os << *p; break; + } + ++p; + } + os << '"'; +} + +void writeViewName(std::ostream& os, const FrameGraphInfo& frameGraph) { + os << " \"viewName\": "; + writeJSONString(os, frameGraph.getViewName()); + os << ",\n"; +} + +void writeResourceIds(std::ostream& os, const std::vector& resources) { + for (size_t j = 0; j < resources.size(); ++j) { + os << resources[j]; + if (j + 1 < resources.size()) os << ", "; + } +} + +void writePasses(std::ostream& os, const FrameGraphInfo& frameGraph) { + os << " \"passes\": [\n"; + auto& passes = frameGraph.getPasses(); + for (size_t i = 0; i < passes.size(); ++i) { + const FrameGraphInfo::Pass& pass = passes[i]; + os << " {\n"; + os << " \"name\": "; + writeJSONString(os, pass.name.c_str()); + os << ",\n"; + + const std::vector& reads = pass.reads; + os << " \"reads\": ["; + writeResourceIds(os, reads); + os << "],\n"; + + const std::vector& writes = pass.writes; + os << " \"writes\": ["; + writeResourceIds(os, writes); + os << "]\n"; + + os << " }"; + if (i + 1 < passes.size()) os << ","; + os << "\n"; + } + os << " ],\n"; +} + +void writeResources(std::ostream& os, const FrameGraphInfo& frameGraph) { + os << " \"resources\": {\n"; + size_t resourceCount = 0; + auto& resources = frameGraph.getResources(); + for (const auto& [id, resource] : resources) { + os << " \"" << id << "\": {\n"; + os << " \"id\": " << resource.id << ",\n"; + os << " \"name\": "; + writeJSONString(os, resource.name.c_str()); + os << ",\n"; + + os << " \"properties\": [\n"; + for (size_t j = 0; j < resource.properties.size(); ++j) { + const auto& [key, value] = resource.properties[j]; + os << " {\n"; + os << " \"key\": "; + writeJSONString(os, key.c_str()); + os << ",\n"; + os << " \"value\": "; + writeJSONString(os, value.c_str()); + os << "\n }"; + if (j + 1 < resource.properties.size()) os << ","; + os << "\n"; + } + os << " ]\n"; + os << " }"; + if (++resourceCount < resources.size()) os << ","; + os << "\n"; + } + os << " }\n"; +} +} // anonymous + +const char* JsonWriter::getJsonString() const { + return mJsonString.c_str(); +} + +size_t JsonWriter::getJsonSize() const { + return mJsonString.size(); +} + +bool JsonWriter::writeFrameGraphInfo(const FrameGraphInfo& frameGraph) { + std::ostringstream os; + os << "{\n"; + + writeViewName(os, frameGraph); + writePasses(os, frameGraph); + writeResources(os, frameGraph); + + os << "}\n"; + + mJsonString = utils::CString(os.str().c_str()); + return true; +} + +} // namespace filament::fgviewer